多线程并发不等于性能提升,关键在于合理控制线程数以匹配负载、减少上下文切换与阻塞。CPU密集型任务线程数≤CPU核心数,IO密集型可设为2~4倍,混合型需拆分处理;须杜绝线程泄漏、隐式阻塞,显式关闭ExecutorService,避免静态工厂创建不可控线程池;优先使用无锁结构、缩小同步范围;上线前须通过vmstat、jstack及监控工具验证线程状态与调度压力。

多线程并发不等于性能提升,上下文切换过高反而会让程序变慢、延迟飙升、CPU空转。关键不是“能不能开多线程”,而是“要不要开、开多少、怎么开”。核心思路是:让线程数匹配实际负载,避免调度压垮CPU,同时减少线程阻塞和状态变更。
按任务类型精准控制线程池大小
线程数远超CPU核心数(比如16核启200线程),操作系统忙于保存/恢复寄存器、程序计数器、栈指针,真正干活的时间反而被挤压。实测中cs(context switch)持续高于10k/s,基本就是调度瓶颈。
- CPU密集型任务(如数值计算、图像处理):线程数 ≤ Runtime.getRuntime().availableProcessors(),甚至用ForkJoinPool.commonPool()这类工作窃取池更稳
- IO密集型任务(如HTTP调用、数据库查询):可设为核心数 ×2~×4,但绝不能无脑填Integer.MAX_VALUE
- 混合型任务建议拆分:CPU部分走固定池,IO部分走独立缓存池或带队列的ThreadPoolExecutor
杜绝线程泄漏与隐式阻塞
线程池没关、任务里混同步块、CompletableFuture用错执行器——这些都会让线程卡住、堆积、最终耗尽资源。
- ExecutorService必须显式释放:声明为final成员变量,配合@PreDestroy(Spring)或try-with-resources(需实现AutoCloseable)
- 别在工具类里写Executors.newFixedThreadPool(10):静态工厂返回的池无法外部控制生命周期,易引发内存缓慢上涨甚至OOM
- 阻塞操作(JDBC查询、Socket读取、synchronized块)必须隔离:用专用IO线程池,例如Executors.newCachedThreadPool(),并在CompletableFuture链中显式指定thenApplyAsync(fn, ioExecutor)
减少锁竞争与主动让出行为
线程因争锁进入BLOCKED、因wait()进入WAITING、因sleep()主动挂起,都会触发上下文切换。频繁进出这些状态,等于反复打断执行流。
立即学习“Java免费学习笔记(深入)”;
- 缩小同步范围:只锁真正共享的字段,避免整个方法或大段逻辑加synchronized
- 优先用CAS、原子类(AtomicInteger)、无锁队列(ConcurrentLinkedQueue)替代悲观锁
- 避免在Runnable中调用Thread.sleep()、Object.wait()、Thread.join();改用ScheduledExecutorService或CompletableFuture.delayedExecutor()
- 检查jstack输出,若大量出现java.lang.Thread.State: WAITING (on object monitor),说明同步块正在成为瓶颈
上线前必做的三件事
配置再合理,没验证就是纸上谈兵。压测和线上观测才是最终判据。
- 用vmstat 1盯住cs列:稳定运行时应远低于5k/s,持续高于10k/s就要调小线程数或拆任务
- 用jstack <pid>抓线程快照:确认没有大量WAITING或TIMED_WAITING状态的pool-*.thread-*线程堆积
- 结合Arthas或Prometheus监控线程池活跃数、队列长度、拒绝数:发现排队激增,说明线程数不够或任务响应慢,而非盲目扩容



















