话不多说,先看代码

public static void main(String[] args) throws Exception {
    // 创建线程池,核心线程和最大线程数都为3,任务队列大小为1
    ThreadPoolExecutor taskPoolExecutor = new ThreadPoolExecutor(3, 3, 30, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1));
    // 预先初始化3个核心线程
    taskPoolExecutor.prestartAllCoreThreads();

    // 提交2个任务,异常:RejectedExecutionException,理论上有可能不出现异常,但实际运行时,基本上是必现的
    for (int i = 0; i < 2; i++) {
        final int finalI = i;
        taskPoolExecutor.execute(() -> {
            System.out.println(Thread.currentThread().getName() + " : " + finalI);
        });
    }
}

示例代码中,创建了核心线程和最大线程数都为3,任务队列大小为1的线程池,但只提交了2个任务,就抛出了RejectedExecutionException异常,结果有些出乎意料。

但分析了线程池的执行过程后,一切又都是合情合理的。

任务入队流程

新的线程池,核心线程初始化后,活动线程数等于核心线程数,新的任务直接进入阻塞队列,因为阻塞队列的大小只有1,第一个任务还没被任务线程取出处理时,第二个任务尝试入队失败,而活动线程数等于最大线程数,不能再创建新的线程处理任务,执行默认拒绝策略,抛出异常。

总结

当前活动线程数大于等于最大线程数时,即使这些活动线程当前是空闲状态,新加入的任务(未被活动线程及时取出处理)超出队列大小限制,线程池即执行拒绝策略

任务执行流程

  • 当前线程数 < 核心线程数

    提交任务时,创建新的线程处理这个任务

  • 当前线程数 >= 核心线程数

    提交任务时,尝试把任务加入阻塞队列,加入成功结束,等待活动线程从队列中取出任务处理

  • 当前线程数 >= 核心线程数 && 加入阻塞队列失败 && 当前线程数 < 最大线程数

    提交任务时,创建新的线程处理这个任务

  • 当前线程数 >= 核心线程数 && 加入阻塞队列失败 && 当前线程数 >= 最大线程数

    提交任务时,执行拒绝策略