CPU密集型任务线程数应设为CPU逻辑核心数,IO密集型需据等待时间估算,混合型应分池隔离,队列须有界并配合理拒绝策略。

CPU密集型任务:别让线程数超过可用CPU核心数
CPU密集型任务(比如图像处理、数值计算)几乎不等待IO,线程一旦启动就持续占用CPU。此时线程数过多只会导致频繁上下文切换,反而拖慢整体吞吐。
- 核心线程数建议设为
Runtime.getRuntime().availableProcessors(),即逻辑核心数(含超线程) - 如果任务偶尔有轻量IO(如小文件读取),可加1~2个缓冲,但绝不推荐翻倍
- 用
Executors.newFixedThreadPool(n)时,n就该是这个值;若用ThreadPoolExecutor,corePoolSize和maximumPoolSize通常设为相等,避免动态扩容带来的不确定性 - 常见错误:把8核机器配成32线程池,监控显示CPU利用率100%但吞吐没提升——本质是调度开销吃掉了收益
IO密集型任务:线程数不是靠猜,得看平均等待时间
IO密集型(如HTTP调用、数据库查询)大部分时间在等响应,线程空闲期长,可以多开些线程来“填满”CPU空档。但盲目堆数量会导致内存压力和连接耗尽。
- 经验公式:
corePoolSize ≈ CPU核心数 × (1 + 平均IO等待时间 / 平均CPU工作时间) - 更实用的估算方式:先压测,观察在不同线程数下,系统吞吐是否还在上升;当吞吐持平或下降,再回退1~2个线程就是较优值
- 注意:数据库连接池(如HikariCP)和HTTP客户端(如OkHttp)本身也有并发限制,线程池过大但下游扛不住,会堆积大量
WAITING线程,日志里常看到java.lang.Thread.State: WAITING (parking) - 别直接套用“2N”这种模糊说法——N是啥?是物理核还是逻辑核?等待时间是毫秒级还是秒级?脱离场景等于没说
混合型任务:拆不干净,就用异步+分池隔离
现实业务很少纯CPU或纯IO,比如一个接口既要算MD5(CPU),又要查三次DB(IO)。这时候统一配一个线程池,参数永远是个妥协。
- 优先按主责拆分:把计算类操作交给CPU优化池,远程调用类交给IO优化池,用
CompletableFuture.supplyAsync(..., executor)显式指定 - 避免在同一个
ThreadPoolExecutor中混跑两类任务——CPU型任务可能被IO型任务饿死,尤其当IO线程卡住时,队列积压导致新计算任务迟迟得不到调度 - 如果必须共用,保守起见按IO型配置,但要加监控:重点关注
getActiveCount()和getQueue().size(),持续高位说明CPU型任务正在排队等IO释放线程
别忘了队列和拒绝策略——配错比配少更危险
线程数只是冰山一角,队列类型和拒绝策略直接影响系统面对突发流量时的行为。
立即学习“Java免费学习笔记(深入)”;
-
LinkedBlockingQueue默认无界,看似安全,实则可能OOM:任务持续涌入而消费跟不上,队列无限增长,JVM堆直接爆掉 - 生产环境务必用有界队列(如
new ArrayBlockingQueue(1000)),并配合合理的拒绝策略:AbortPolicy抛异常、CallerRunsPolicy让调用线程自己执行(适合低频突发)、DiscardOldestPolicy丢老任务保新(慎用,可能丢关键数据) - 常见坑:用
Executors.newCachedThreadPool()应对IO型任务——它用SynchronousQueue,不存任务,但最大线程数是Integer.MAX_VALUE,瞬间打满线程数,系统直接假死
ThreadPoolExecutor.getActiveCount() 是否长期接近 getCorePoolSize(),再看队列长度趋势,最后结合GC和线程dump判断瓶颈在哪。公式只是起点,不是终点。










