You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CachedThreadPool为何创建两个线程?调用shutdown()后为何仅创建一个?

问题:CachedThreadPool调用shutdown()前后线程创建数量差异的原因

以下是我的Java代码:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

class ExceptionThread2 implements Runnable {
    @Override
    public void run() {
        Thread t = Thread.currentThread();
        System.out.println("run() by " + t);
        System.out.println("eh = " + t.getUncaughtExceptionHandler());
        throw new RuntimeException();
    }
}

class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println("caught " + e);
    }
}

class HandlerThreadFactory implements ThreadFactory {
    @Override
    public Thread newThread(Runnable r) {
        System.out.println(this + " creating new Thread ");
        Thread t = new Thread(r);
        System.out.println("created " + t);
        t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        System.out.println("eh = " + t.getUncaughtExceptionHandler());
        return t;
    }
}

public class CaptureUncaughtException {
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool(new HandlerThreadFactory());
        exec.execute(new ExceptionThread2());
        //exec.shutdown();
    }
}

运行结果(未取消注释exec.shutdown()):

com.concurrent.example.HandlerThreadFactory@7f31245a creating new Thread 
created Thread[Thread-0,5,main] 
eh = com.concurrent.example.MyUncaughtExceptionHandler@6d6f6e28 
run() by Thread[Thread-0,5,main] 
eh = com.concurrent.example.MyUncaughtExceptionHandler@6d6f6e28 
com.concurrent.example.HandlerThreadFactory@7f31245a creating new Thread 
created Thread[Thread-1,5,main] 
eh = com.concurrent.example.MyUncaughtExceptionHandler@2870fdbb 
caught java.lang.RuntimeException

运行结果(取消注释exec.shutdown()):

com.concurrent.example.HandlerThreadFactory@7f31245a creating new Thread 
created Thread[Thread-0,5,main] 
eh = com.concurrent.example.MyUncaughtExceptionHandler@6d6f6e28 
run() by Thread[Thread-0,5,main] 
eh = com.concurrent.example.MyUncaughtExceptionHandler@6d6f6e28 
caught java.lang.RuntimeException

请问这是什么原因?


解答

这个差异的核心原因在于ThreadPoolExecutor(CachedThreadPool的底层实现)对异常终止线程的处理逻辑,以及shutdown()方法对线程池状态的改变。

1. 未调用shutdown()时创建第二个线程的原因

当线程池处于默认的RUNNING状态时:

  • 你提交的ExceptionThread2任务被分配给Thread-0执行,任务抛出RuntimeException导致Thread-0异常终止(不是正常完成任务)。
  • ThreadPoolExecutor的processWorkerExit方法会处理这个异常终止的Worker线程:
    • 首先将已死亡的Worker从线程池的管理集合中移除。
    • 由于线程是突然终止的,方法最后会调用addWorker(null, false)尝试添加一个新的Worker线程(这就是Thread-1被创建的原因)。
    • 此时任务队列是空的,新创建的Thread-1不会执行任何任务,会进入空闲等待状态,直到60秒后被自动回收(CachedThreadPool的空闲线程超时时间默认是60秒)。

2. 调用shutdown()后不再创建新线程的原因

当你调用exec.shutdown()后,线程池的状态会切换为SHUTDOWN:

  • 同样,Thread-0执行任务时抛出异常并终止。
  • 在processWorkerExit方法中,虽然线程是突然终止,但此时线程池处于SHUTDOWN状态:
    • addWorker(null, false)方法会检查线程池状态和任务队列,在SHUTDOWN状态下,只有当任务队列非空时才会创建新的Worker线程,而此时队列是空的,所以不会创建新线程。
  • 线程池会等待所有已提交的任务完成后,逐步关闭现有线程,不会再分配新的线程资源。

补充:CachedThreadPool的核心特性

Executors.newCachedThreadPool()的底层是ThreadPoolExecutor,配置了以下关键参数:

  • 核心线程数为0,最大线程数为Integer.MAX_VALUE
  • 使用SynchronousQueue作为任务队列(任务直接传递给线程,不会在队列中等待)
  • 空闲线程超过60秒会被自动回收

这种配置让它能根据任务量动态创建线程,但当线程因异常终止且线程池处于RUNNING状态时,会自动补充新的Worker线程以维持线程池的可用性。


内容的提问来源于stack exchange,提问作者bfq

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:11:29