ExecutorService 提交大量任务后响应变慢,主因是任务排队、上下文切换和锁竞争;常见表现为 Future.get() 长时间阻塞或 completedTaskCount 增长缓慢,需检查无界队列、同步日志、拒绝策略,并用 jstack/VisualVM 定位锁争用点,避免 ForkJoinPool.commonPool() 用于 IO 场景。

为什么 ExecutorService 提交大量任务后响应变慢?
不是线程池不够大,而是任务排队、上下文切换和锁竞争在悄悄拖垮性能。常见表现是 submit() 调用不卡,但 Future.get() 长时间阻塞,或 ThreadPoolExecutor.getCompletedTaskCount() 增长极慢。
- 检查是否用了
LinkedBlockingQueue且未设容量上限——默认无界队列会吃光堆内存,GC 频繁拖慢整体节奏 - 避免在任务中调用
System.out.println()或同步日志——PrintStream内部是 synchronized 方法,高并发下成热点锁 - 确认拒绝策略:用
AbortPolicy能快速暴露过载,而CallerRunsPolicy表面“不丢任务”,实则把压力转嫁回主线程,掩盖真实瓶颈
如何用 jstack 和 VisualVM 定位线程阻塞点?
别等服务超时再查,要在压测中实时抓取。重点不是看“有多少线程”,而是看“哪些线程在等什么”。
本程序源码为asp与acc编写,并没有花哨的界面与繁琐的功能,维护简单方便,只要你有一些点点asp的基础,二次开发易如反掌。 1.功能包括产品,新闻,留言簿,招聘,下载,...是大部分中小型的企业建站的首选。本程序是免费开源,只为大家学习之用。如果用于商业,版权问题概不负责。1.采用asp+access更加适合中小企业的网站模式。 2.网站页面div+css兼容目前所有主流浏览器,ie6+,Ch
-
jstack -l输出里搜java.lang.Thread.State: BLOCKED,接着看waiting to lock对应的锁地址,再往上翻找哪个线程正持有它 - 在
VisualVM的 “Threads” 标签页,开启“Monitor CPU”后点击 “Thread Dump”,对比两次 dump 中长期处于WAITING (on object monitor)的线程栈 - 特别注意
ReentrantLock.lock()、ConcurrentHashMap.computeIfAbsent()(某些 JDK 版本在 hash 冲突严重时会锁住整个 bin)、CopyOnWriteArrayList.add()这些看似线程安全、实则隐含锁粒度陷阱的操作
ForkJoinPool.commonPool() 为什么在 IO 密集场景下反而更慢?
它的设计目标是 CPU 密集型递归任务(如分治排序、并行流处理数组),工作窃取机制对随机延迟的 IO 操作完全无效,还增加调度开销。
- 遇到数据库查询、HTTP 调用、文件读写,必须显式创建独立的
ExecutorService,比如Executors.newFixedThreadPool(10),并控制最大并发数匹配下游承载力 -
CompletableFuture.supplyAsync(() -> {...}, executor)必须传入自定义executor,否则默认走commonPool(),哪怕你写了thenApplyAsync也逃不掉 - JDK 19+ 引入了
StructuredTaskScope,更适合 IO 并发编排,但需配合虚拟线程(Thread.ofVirtual())使用,普通平台线程仍不适用
// 错误:全部跑在 commonPool,IO 任务卡住整个池
CompletableFuture.supplyAsync(() -> httpClient.get("/api/user"))
.thenApplyAsync(data -> parseJson(data));
// 正确:IO 专用线程池,CPU 密集操作才进 commonPool
ExecutorService ioPool = Executors.newFixedThreadPool(8);
CompletableFuture.supplyAsync(() -> httpClient.get("/api/user"), ioPool)
.thenApplyAsync(data -> parseJson(data)); // 这里没指定 executor,parseJson 是 CPU 密集,可放心用 commonPool
真正卡住并发性能的,往往不是线程数配得少,而是没意识到某个 ConcurrentHashMap 的 key 设计导致所有写操作挤在同一个 segment 上,或者一个本该异步的日志回调被错误地塞进了同步执行链路。这些细节不会报错,只会让吞吐量在 200 QPS 和 2000 QPS 之间反复横跳。










