当前位置: 首页 > news >正文

一、ThreadPoolExecutor vs ThreadPoolTaskExecutor

目录

本质关系

从原理看区别

从方法看区别

从实际使用看区别

总结一句话

二、公共池是谁在用?为什么一个任务慢会卡住其他的?

公共池是谁创建的?

公共池在哪?

为什么一个任务慢会卡住其他的?

实际项目中的隐患

所以为什么不要用公共池

总结


本质关系

ThreadPoolTaskExecutor 是 Spring 对 ThreadPoolExecutor 的封装
┌─────────────────────────────────────────┐ │ ThreadPoolTaskExecutor │ ← Spring封装层 │ 管理生命周期、支持YAML配置、支持@Async │ │ │ │ ┌───────────────────────────────────┐ │ │ │ ThreadPoolExecutor │ │ ← JDK原生 │ │ 线程池核心逻辑: │ │ │ │ - 线程调度 │ │ │ │ - 队列管理 │ │ │ │ - 拒绝策略 │ │ │ └───────────────────────────────────┘ │ └─────────────────────────────────────────┘

从原理看区别

维度ThreadPoolExecutorThreadPoolTaskExecutor
来源JDK原生(java.util.concurrent)Spring封装(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要手动shutdown+awaitTermination) 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(); // 队列中等待的任务数

从实际使用看区别

场景一:项目标准配置(推荐用ThreadPoolTaskExecutor)

@Configuration @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项目ThreadPoolTaskExecutor+@Async
通用Java项目、工具类库ThreadPoolExecutor
需要动态调整线程数/监控ThreadPoolTaskExecutor+getThreadPoolExecutor()

二、公共池是谁在用?为什么一个任务慢会卡住其他的?

公共池是谁创建的?

不是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卡住了:

线程1:callServiceA(远程接口超时,等了10秒还没返回) 线程2:callServiceB(正常,2秒完成) 线程3:callServiceC(正常,2秒完成) ​ → 线程2、3完成后空闲 → callServiceD 占到线程2,开始执行 ​ 看起来还行?但如果同时来5个任务呢: ​ 线程1:callServiceA(卡住10秒) 线程2:callServiceB(2秒完成)→ callServiceE(2秒完成)→ callServiceF(等线程1) 线程3:callServiceC(2秒完成)→ callServiceG(2秒完成)→ 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个线程都被占着,其他异步任务只能排队等。所以实际项目中我不会用公共池,而是按业务场景创建独立的线程池,互不影响。

http://www.jsqmd.com/news/1101878/

相关文章:

  • 基于甲基化芯片与靶向捕获测序的DNA甲基化分析技术综述
  • 浏览器资源嗅探神器:如何优雅捕获网页中隐藏的媒体宝藏
  • 英雄联盟Akari助手:5分钟快速上手的终极游戏效率工具指南
  • 空调能效评价进入“动态时代”:为什么电流检测正在成为变频控制的新变量?
  • OpenAI产品生态暗藏的5个未公开能力(含即将下线的Legacy Endpoint预警)
  • 077、GroupConv 分组数消融实验:g=2/4/8/16 对精度与延迟的影响曲线
  • d2s-editor:3分钟掌握暗黑破坏神2存档编辑技巧,打造完美角色!
  • STM32矩阵键盘扩展方案:用74HC32实现8功能控制
  • ntfy-android附件下载失败排查指南:配置映射错误的技术解析
  • 网站做不好?因为你忽略了这最关键的一步:顶层设计
  • 基于KMR221与PIC18LF4620的嵌入式电压管理系统设计
  • vLLM 与 SGLang 推理框架性能横评
  • B站视频备份神器:m4s-converter轻松实现缓存视频永久保存
  • 为什么92%的开发者误用OpenAI Assistant API?3个高频错误配置与性能优化黄金参数
  • 云计算短缺,谷歌限制Meta访问Gemini,加速Meta模型自主研发进程
  • 免费哔咔漫画下载器完整指南:3步打造个人永久漫画库
  • 如何快速构建个人漫画图书馆:哔咔漫画下载器完整指南
  • HS2-HF Patch完整汉化教程:3步快速实现HoneySelect2完美体验
  • 技术享元中的对象共享与状态外部化
  • 6DoF运动跟踪技术:从IMU到STM32实现
  • 基于ICM-42605和PIC18的6DOF运动追踪系统设计
  • 遗传算法实战:N皇后问题的工程化实现与性能优化
  • ChatGPT Memory功能实战避坑指南,12个真实生产环境崩溃案例(含OpenAI官方未公开日志片段)
  • 【限时技术预警】ChatGPT Memory Beta版已悄然关闭旧会话自动清理——你的对话数据正被永久留存?
  • 蛋白质组学视野下的 DARTS:如何实现高通量靶标筛选
  • TDMS格式查看
  • 基于PCF8591与TM4C129的双模信号转换系统设计
  • 别再只调ChatGPT了!用Python+通义千问API,5分钟给你的小工具加上AI对话能力
  • FLAME 技术详解:3D 人脸模型里的“骨架、身份和表情”到底怎么拆开
  • 4-20mA电流环工业应用与DAC161S997方案解析