一、ThreadPoolExecutor vs ThreadPoolTaskExecutor 目录本质关系从原理看区别从方法看区别从实际使用看区别总结一句话二、公共池是谁在用为什么一个任务慢会卡住其他的公共池是谁创建的公共池在哪为什么一个任务慢会卡住其他的实际项目中的隐患所以为什么不要用公共池总结本质关系ThreadPoolTaskExecutor 是 Spring 对 ThreadPoolExecutor 的封装┌─────────────────────────────────────────┐ │ ThreadPoolTaskExecutor │ ← Spring封装层 │ 管理生命周期、支持YAML配置、支持Async │ │ │ │ ┌───────────────────────────────────┐ │ │ │ ThreadPoolExecutor │ │ ← JDK原生 │ │ 线程池核心逻辑 │ │ │ │ - 线程调度 │ │ │ │ - 队列管理 │ │ │ │ - 拒绝策略 │ │ │ └───────────────────────────────────┘ │ └─────────────────────────────────────────┘从原理看区别维度ThreadPoolExecutorThreadPoolTaskExecutor来源JDK原生java.util.concurrentSpring封装spring-context内部实现直接管理线程和队列内部持有一个ThreadPoolExecutor生命周期需要手动shutdownSpring自动管理Bean销毁时自动关闭配置方式Java代码硬编码支持application.yml配置任务装饰无支持TaskDecorator给任务加前后逻辑和Spring集成无感知与Async、EnableAsync深度集成线程命名需要自己写ThreadFactorysetThreadNamePrefix一行搞定从方法看区别ThreadPoolTaskExecutor 额外提供的方法ThreadPoolTaskExecutor taskExecutor new ThreadPoolTaskExecutor(); ​ // ① 线程名前缀ThreadPoolExecutor要自己写ThreadFactory taskExecutor.setThreadNamePrefix(async-); // 效果线程名变成 async-1, async-2, async-3 ​ // ② 关闭时等待任务完成ThreadPoolExecutor要手动shutdownawaitTermination taskExecutor.setWaitForTasksToCompleteOnShutdown(true); taskExecutor.setAwaitTerminationSeconds(30); ​ // ③ 任务装饰器给每个任务执行前后加逻辑 taskExecutor.setTaskDecorator(r - { // 保存当前线程的上下文比如MDC、ThreadLocal MDC.put(traceId, UUID.randomUUID().toString()); return () - { try { r.run(); } finally { MDC.clear(); } }; }); ​ // ④ 手动执行Spring特有 taskExecutor.execute(() - doSomething()); ​ // ⑤ 停止后不接受新任务 taskExecutor.setRejectedExecutionHandler( new ThreadPoolExecutor.CallerRunsPolicy()); ​ // 应用启动后才会初始化初始化后返回内部的ThreadPoolExecutor taskExecutor.initialize(); ​ // 获取内部原生的ThreadPoolExecutor需要时可以拿到 ThreadPoolExecutor raw taskExecutor.getThreadPoolExecutor();ThreadPoolExecutor 没有以上方法只有ThreadPoolExecutor executor new ThreadPoolExecutor(...); executor.execute(task); // 执行 executor.submit(task); // 提交带返回值 executor.shutdown(); // 不接受新任务等已提交的完成 executor.shutdownNow(); // 立即停止返回未执行的任务列表 executor.isShutdown(); // 是否已调用shutdown executor.isTerminated(); // 是否所有任务都已完成 executor.getActiveCount(); // 当前活跃线程数 executor.getQueue().size(); // 队列中等待的任务数从实际使用看区别场景一项目标准配置推荐用ThreadPoolTaskExecutorConfiguration EnableAsync public class ThreadPoolConfig { ​ Bean(asyncExecutor) public ThreadPoolTaskExecutor asyncExecutor() { ThreadPoolTaskExecutor e new ThreadPoolTaskExecutor(); e.setCorePoolSize(8); e.setMaxPoolSize(16); e.setQueueCapacity(200); e.setThreadNamePrefix(async-); e.setWaitForTasksToCompleteOnShutdown(true); e.setAwaitTerminationSeconds(30); e.initialize(); return e; } } ​ // 业务代码 Service public class OrderService { Async(asyncExecutor) public void sendNotification(Long orderId) { ... } }场景二需要精细控制时拿内部原生对象Autowired private ThreadPoolTaskExecutor taskExecutor; ​ public void someMethod() { // 拿到内部的ThreadPoolExecutor ThreadPoolExecutor raw taskExecutor.getThreadPoolExecutor(); // 可以动态调整线程数 raw.setCorePoolSize(16); raw.setMaximumPoolSize(32); // 可以实时监控 log.info(活跃线程{}, raw.getActiveCount()); log.info(队列积压{}, raw.getQueue().size()); log.info(已完成任务{}, raw.getCompletedTaskCount()); }场景三不用Spring的通用Java项目用ThreadPoolExecutor// 没有Spring没有Configuration纯Java ThreadPoolExecutor executor new ThreadPoolExecutor( 8, 16, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue(1000), r - new Thread(r, worker- counter.incrementAndGet()) ); ​ executor.execute(() - doSomething()); ​ // 应用关闭时手动销毁 Runtime.getRuntime().addShutdownHook(new Thread(() - { executor.shutdown(); executor.awaitTermination(10, TimeUnit.SECONDS); }));总结一句话场景用什么Spring Boot项目ThreadPoolTaskExecutorAsync通用Java项目、工具类库ThreadPoolExecutor需要动态调整线程数/监控ThreadPoolTaskExecutorgetThreadPoolExecutor()二、公共池是谁在用为什么一个任务慢会卡住其他的公共池是谁创建的不是JVM自动创建的。是你调用了某些API时JVM懒加载创建的。// 这两个操作会触发公共池创建 CompletableFuture.supplyAsync(() - ...); // 不指定线程池时 list.parallelStream().map(...); // 并行流触发时机// 第一次调用时JVM内部创建公共池 ForkJoinPool commonPool ForkJoinPool.commonPool(); // 线程数 Runtime.getRuntime().availableProcessors() - 1 // 比如4核CPU → 3个线程8核 → 7个线程公共池在哪┌──────────────────────────────────────────┐ │ JVM 进程 │ │ │ │ ┌──────────────────────────────────┐ │ │ │ ForkJoinPool.commonPool() │ │ ← 全局唯一静态实例 │ │ 3个线程4核CPU │ │ │ │ │ │ │ │ 谁在用 │ │ │ │ ① CompletableFuture 不指定线程池 │ │ │ │ ② parallelStream 并行流 │ │ │ │ ③ ForkJoinTask │ │ │ │ ④ 框架内部Spring Data等 │ │ │ └──────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────┐ │ │ │ 你的自定义线程池async等 │ │ │ └──────────────────────────────────┘ │ └──────────────────────────────────────────┘为什么一个任务慢会卡住其他的因为公共池的线程是共享的只有3-7个一个任务占住线程不释放其他任务就只能等。用4核CPU举例公共池只有3个线程 // 三个任务同时提交 CompletableFuture.supplyAsync(() - callServiceA()); // 占线程1 CompletableFuture.supplyAsync(() - callServiceB()); // 占线程2 CompletableFuture.supplyAsync(() - callServiceC()); // 占线程3 ​ // 现在第4个任务来了 CompletableFuture.supplyAsync(() - callServiceD()); // 没有空闲线程了 // → callServiceD 必须等 callServiceA/B/C 有一个执行完才轮到它如果callServiceA卡住了线程1callServiceA远程接口超时等了10秒还没返回 线程2callServiceB正常2秒完成 线程3callServiceC正常2秒完成 ​ → 线程2、3完成后空闲 → callServiceD 占到线程2开始执行 ​ 看起来还行但如果同时来5个任务呢 ​ 线程1callServiceA卡住10秒 线程2callServiceB2秒完成→ callServiceE2秒完成→ callServiceF等线程1 线程3callServiceC2秒完成→ callServiceG2秒完成→ callServiceH等线程1 线程1释放后 → callServiceD终于开始执行 ​ callServiceD等了8秒才开始原本只需要2秒而且更严重的情况parallelStream也用公共池// 业务代码1 CompletableFuture.supplyAsync(() - callServiceA()); // 占线程1 ​ // 业务代码2完全不相关的模块 list.parallelStream() .map(item - queryDB(item)) // 也要占线程 .collect(Collectors.toList()); ​ // 两个模块互相抢线程性能互相拖累实际项目中的隐患公共池3个线程被占满的场景 ​ 1. 三个CompletableFuture在调远程接口其中一个超时卡了10秒 → 第4个CompletableFuture排队等10秒 → 响应时间从200ms飙到10秒 ​ 2. parallelStream在做大数据查询占了2个线程 → 只剩1个线程给CompletableFuture用 → 所有异步任务都变慢 ​ 3. 框架内部也用公共池比如Spring Data的一些异步操作 → 和你的业务代码抢线程所以为什么不要用公共池公共池的问题 ① 线程数太少CPU核心数-1 ② 全局共享所有任务互相抢线程 ③ 一个任务卡住影响其他任务 ④ 无法单独调优不能改线程数、队列大小 自定义线程池的好处 ① 线程数按场景设置 ② 业务隔离互不影响 ③ 可以单独监控和调优 ④ 拒绝策略可以按场景选择总结ThreadPoolTaskExecutor是Spring对JDK原生ThreadPoolExecutor的封装。内部还是ThreadPoolExecutor但额外提供了生命周期管理、线程名前缀、任务装饰器、Async集成等功能。Spring项目推荐用ThreadPoolTaskExecutor通用Java项目用ThreadPoolExecutor。公共池是JVM的一个全局共享线程池当你使用CompletableFuture不指定线程池或者用parallelStream时会自动使用。它的线程数默认只有CPU核心数-1比如4核只有3个线程。一个任务慢会卡住其他的因为线程是共享的。比如三个任务同时用公共池其中一个调远程接口超时卡了10秒这10秒内那3个线程都被占着其他异步任务只能排队等。所以实际项目中我不会用公共池而是按业务场景创建独立的线程池互不影响。