线程池大小需按任务类型设定:CPU密集型≈核心数+1~2,IO密集型建议核心数×2~8并优先异步IO,混合型应隔离线程池;禁用newFixedThreadPool和newCachedThreadPool,须用有界队列的自定义ThreadPoolExecutor。

线程池大小设多少才不拖慢吞吐量
盲目加大 ThreadPoolExecutor 的核心线程数,反而会因上下文切换开销和锁竞争导致吞吐下降。关键不是“越多越好”,而是匹配任务类型:
- CPU 密集型任务:线程数 ≈ CPU 核心数(
Runtime.getRuntime().availableProcessors()),再加 1~2 个冗余线程防阻塞 - IO 密集型任务:需实测,常见范围是核心数 × 2~8;但更稳妥的做法是用异步 IO(如
CompletableFuture+ExecutorService配合非阻塞网络库)替代纯线程扩容 - 混合型任务:拆分任务类型,用不同线程池隔离,避免慢 IO 任务饿死快计算任务
注意:newFixedThreadPool 和 newCachedThreadPool 在高负载下极易失控——前者队列无界可能 OOM,后者线程无上限易触发频繁 GC。生产环境必须用带界队列、可监控的自定义 ThreadPoolExecutor。
BlockingQueue 选错直接卡死吞吐
LinkedBlockingQueue 默认容量是 Integer.MAX_VALUE,看似“无限”,实则会让拒绝策略失效,任务堆积后内存暴涨、GC 停顿加剧;而 ArrayBlockingQueue 容量固定,配合 AbortPolicy 或自定义拒绝策略(如记录日志 + 降级返回)才能暴露瓶颈。
- 高吞吐写场景:优先选
ArrayBlockingQueue,显式控制积压上限 - 需要动态扩容且能接受轻微延迟:用
SynchronousQueue(实际是“手递手”传递),配合newCachedThreadPool的扩容逻辑,但仅适用于短时突发、平均负载低的场景 - 绝对不要用
PriorityBlockingQueue做任务队列——排序开销大,且破坏 FIFO 公平性,吞吐常下降 30%+
volatile 和 synchronized 不解决所有并发问题
很多人以为加了 volatile 就线程安全,或用 synchronized 包住整个方法就万事大吉。但吞吐瓶颈往往藏在这些细节里:
立即学习“Java免费学习笔记(深入)”;
-
volatile只保证可见性和禁止重排序,不保证原子性——counter++这种操作即使字段是volatile,依然会丢数据 -
synchronized方法锁的是当前实例(this),若多个线程操作不同对象,根本没竞争;若锁太粗(比如整个 service 方法),会串行化本可并行的逻辑 - 高频读写计数类场景,优先用
LongAdder而非AtomicLong,前者通过分段累加减少争用,在多核下吞吐可提升 5–10 倍
ThreadLocal 用不好反而吃掉堆内存
ThreadLocal 是提升单线程内复用效率的好工具,但在线程池场景下极易引发内存泄漏——线程复用导致 ThreadLocal 的 value 无法被回收,尤其 value 是大对象或持有外部引用时。
- 务必在业务逻辑结束前调用
threadLocal.remove(),不能只依赖set(null) - 避免在
Runnable中直接捕获外部ThreadLocal变量,推荐封装成Supplier并在执行前后显式清理 - 使用
TransmittableThreadLocal(阿里 TTL 库)解决线程池中父子线程传递问题,但要注意它本身也有性能开销,仅在必要时启用
真正影响吞吐的,常常不是算法复杂度,而是这些看似“细小”的资源生命周期管理。线程、队列、锁、本地变量——每个环节的松动,都会在高并发下被指数级放大。











