按 Java 语言规范中的说法,创建线程只有一种方式,就是创建一个 Thread 对象。而从 HotSpot 虚拟机的角度看,创建一个虚拟机线程 有两种方式,一种是创建 Thread 对象,另一种是创建 一个本地线程,加入到虚拟机线程中。
如果从 Java 语法的角度。有两种方法。
第一是继承 Thread 类,实现 run 方法,并创建子类对象。
public void startThreadUseSubClass() {
trueclass MyThread extends Thread {
truetruepublic void run() {
truetruetrueSystem.out.println("start thread using Subclass of Thread");
truetrue}
true}
trueMyThread thread = new MyThread();
truethread.start();
}
另一种是传递给 Thread 构造函数一个 Runnable 对象。
public void startThreadUseRunnalbe() {
trueThread thread = new Thread(new Runnable() {
truetruepublic void run() {
truetruetrueSystem.out.println("start thread using runnable");
truetrue}
true});
truethread.start();
}
当然, Runnalbe 对象,也不是只有这一种形式,例如如果我们想要线程执行时返回一个值,就需要用到另一种 Runnalbe 对象,它 对原来的 Runnalbe 对象进行了包装。
public void startFutureTask() {
trueFutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
truetruepublic Integer call() {
truetruetruereturn 1;
truetrue}
true});
truenew Thread(task).start();
truetry {
truetrueInteger result = task.get();
truetrueSystem.out.println("future result " + result);
true} catch (InterruptedException e) {
truetruee.printStackTrace();
true} catch (ExecutionException e) {
truetruee.printStackTrace();
true}
}
sleep 会使得当前线程休眠一段时间,但并不会释放已经得到的锁。
wait 会阻塞住,并释放已经得到的锁。一直到有人调用 notify 或者 notifyAll,它会重新尝试得到锁,然后再唤醒。
线程池中有一系列线程,这些线程在执行完任务后,并不会被销毁,而会从任务队列中取出任务,执行这些任务。这样,就避免为每个任务 都创建线程,销毁线程。 在有大量短命线程的场景下,如果创建线程和销毁线程的时间比线程执行任务的时间还长,显然是不划算的,这时候,使用线程池就会有明显 的好处。
同时,可以设置线程数目,这样,线程不会增大到影响系统整体性能的程度。当任务太多时,可以在队列中排队, 如果有空闲线程,他们会从队列中取出任务执行。
那么,线程的数目要设置成多少呢?这需要根据任务类型的不同来设置,假如是大量计算型的任务,他们不会阻塞,那么可以将线程数目设置 为处理器数目。而如果任务中涉及大量IO,有些线程会阻塞住,这样就要根据阻塞线程数目与运行线程数目的比例,以及处理器数目来设置 线程总数目。例如阻塞线程数目与运行线程数目之比为n, 处理器数目为p,那么可以设置 n * (p + 1) 个线程,保证有 n 个线程处于运行 状态。
JDK 的 java.util.concurrent.Executors 类提供了几个静态的方法,用于创建不同类型的线程池。
ExecutorService service = Executors.newFixedThreadPool(10);
ArrayList<Future<Integer>> results = new ArrayList<>();
for (int i = 0; i < 14; i++) {
trueFuture<Integer> r = service.submit(new Callable<Integer>() {
truetruepublic Integer call() {
truetruereturn new Random().nextInt();
true});
trueresults.add(r);
}
newFixedThreadPool
可以创建固定数目的线程,一旦创建不会自动销毁线程,即便长期没有任务。除非显式关闭线程池。如果任务队列中有任务,就取出任务执行。
另外,还可以使用 newCachedThreadPool
方法创建一个不设定固定线程数目的线程池,它有一个特性,线程完成任务后,如果一分钟之内又有新任务,就会复用这个线程执行新任务。如果超过一分钟还没有任务执行,就会自动销毁。
另外,还提供了 newSingleThreadExecutor
创建有一个工作线程的线程池。
JDK 中的线程池通过 HashSet 存储工作者线程,通过 BlockingQueue 来存储待处理任务。
通过核心工作者数目(corePoolSize) 和 最大工作者数目(maximumPoolSize) 来确定如何处理任务。如果当前工作者线程数目 小于核心工作者数目,则创建一个工作者线程执行这个任务。否则,将这个任务放入待处理队列。如果入队失败,再看看当前工作 者数目是不是小于最大工作者数目,如果小于,则创建工作者线程执行这个任务。否则,拒绝执行这个任务。
另外,如果待处理队列中没有任务要处理,并且工作者线程数目超过了核心工作者数目,那么,需要减少工作者线程数目。