每个任务都创建一个线程,会反复产生创建和销毁的开销。线程池把工作线程留下来,让它们不断取任务执行。理解线程池,先看任务提交后去了哪里。

核心线程、队列和最大线程

ThreadPoolExecutor 收到任务后,主要按下面的顺序处理:

  1. 工作线程数小于 corePoolSize,尝试创建线程执行任务。
  2. 达到核心数量后,尝试把任务放进队列。
  3. 队列放不进去,再尝试增加线程,直到 maximumPoolSize。
  4. 仍然无法接收,交给拒绝策略。

所以不是“先把线程开到 maximumPoolSize,再往队列里放”。如果使用容量很大的队列,任务通常会先在队列里等,最大线程数未必有机会发挥作用。

看一下实际分配

下面先让任务等待一个门闩,避免它们太快结束,方便观察线程和队列:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class PoolFlowDemo {
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 30,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(1));
        CountDownLatch release = new CountDownLatch(1);
        Runnable task = () -> {
            try {
                release.await();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        };
        try {
            pool.execute(task);
            pool.execute(task);
            pool.execute(task);
            System.out.println("线程数=" + pool.getPoolSize());
            System.out.println("排队数=" + pool.getQueue().size());
            try {
                pool.execute(task);
            } catch (RejectedExecutionException e) {
                System.out.println("第四个任务被拒绝");
            }
        } finally {
            release.countDown();
            pool.shutdown();
        }
        if (!pool.awaitTermination(5, TimeUnit.SECONDS)) pool.shutdownNow();
    }
}

线程数为 2,排队数为 1,第四个任务被拒绝。第一个任务创建核心线程,第二个入队,第三个因为队列已满而创建第二个线程。

Worker 做什么

JDK 8 用 Worker 管理工作线程。线程启动后进入 runWorker(),先执行创建时携带的第一个任务,然后通过 getTask() 从队列继续取。

超过核心数量的空闲线程通常使用带超时的 poll 获取任务;等待超时且符合退出条件时,工作线程退出。允许核心线程超时后,核心部分也可以参与回收。因此“核心线程”不是给某条 Thread 永久贴上的身份标签。

execute 在任务成功入队后还会重新检查线程池状态。提交和关闭可能同时发生,入队成功不能直接说明线程池仍接受它;此外,如果此时没有工作线程,也需要补建线程处理队列。

shutdown 不等于等完

shutdown() 停止接受新任务,继续处理已提交任务,但方法本身不会等所有任务结束。需要等待时使用 awaitTermination()。

shutdownNow() 尝试中断正在执行的任务,并返回尚未开始的排队任务,也不能保证忽略中断的任务立刻停止。线程池能管理任务的执行过程,任务自身仍需配合取消协议。

最后修改:2018 年 04 月 06 日
如果觉得我的文章对你有用,请随意赞赏