【动辄OOM?这份线程池避坑指南让我薪资翻倍】
【动辄OOM这份线程池避坑指南让我薪资翻倍】面试必问、线上必踩、性能必调——线程池可能是你简历上最危险的一个词项目里用到了线程池用ThreadPoolExecutor配了几个参数跑得挺稳的。这句话你说过吗我见过太多候选人在简历上写熟练使用线程池面试官轻轻一问就原形毕露核心线程数和最大线程数到底啥区别队列满了新任务去哪了核心线程空闲时会被回收吗阿里规范为什么禁止用Executors创建线程池别慌今天把这几个问题彻底讲清楚。一、你以为的参数配置其实全是坑先来看一个经典的线程池配置ExecutorService pool Executors.newFixedThreadPool(10);看起来很标准对吧固定10个线程不多不少。但你知道newFixedThreadPool底层用的是什么队列吗public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueueRunnable()); }用的是LinkedBlockingQueue而这个队列的默认容量是——Integer.MAX_VALUE。也就是说核心线程10个队列容量21亿。正常情况下没问题但一旦任务堆积系统会疯狂往队列里塞最终导致内存暴涨21亿个任务对象等待执行OOM风险队列撑爆堆内存定位困难线程池没有拒绝任务就是不见执行这就是阿里规约要求你用ThreadPoolExecutor显式指定队列的原因。二、ThreadPoolExecutor 七大参数到底怎么配public ThreadPoolExecutor( int corePoolSize, // 核心线程数 int maximumPoolSize, // 最大线程数 long keepAliveTime, // 空闲线程存活时间 TimeUnit unit, // 时间单位 BlockingQueueRunnable workQueue, // 任务队列 ThreadFactory threadFactory, // 线程工厂 RejectedExecutionHandler handler // 拒绝策略 )2.1 核心线程数corePoolSize这是线程池的常驻人口。即使这些线程闲着也不会被回收。配置思路任务类型配置建议原因CPU密集型CPU核心数 1线程本身会占CPU1应对缺页中断IO密集型CPU核心数 × 2或根据W/C公式计算线程大部分时间在等待IOW/C公式来自《Java并发编程实战》N_threads N_cpu × (1 W/C)N_cpuCPU核心数Runtime.getRuntime().availableProcessors()W线程等待时间C线程计算时间// 获取CPU核心数 int cpus Runtime.getRuntime().availableProcessors(); // 假设业务场景等待时间占80%计算时间占20% // W/C 48核机器 → 8 × (1 4) 40个线程2.2 最大线程数maximumPoolSize当核心线程全忙、队列满了系统会继续创建线程来消化任务但最多不超过这个数。经验法则设置为核心线程数的 1.5~2 倍不要设得太大否则大量线程竞争CPU反而降低性能结合压测调整找到拐点2.3 空闲线程存活时间keepAliveTime非核心线程空闲超过这个时间就会被回收。注意只有当线程数 corePoolSize 时这个参数才生效。// 示例非核心线程空闲60秒后回收 new ThreadPoolExecutor(10, 20, 60L, TimeUnit.SECONDS, queue);如果你想让核心线程也能被回收不推荐除非特殊场景// 允许核心线程超时 executor.allowCoreThreadTimeOut(true);三、三种队列怎么选BlockingQueueRunnable workQueue这是最容易出问题的参数。3.1 ArrayBlockingQueue有界队列new ArrayBlockingQueue(100);有界、防OOM需要预估任务量队列满了 → 创建非核心线程 → 也满了 → 触发拒绝策略3.2 LinkedBlockingQueue无界队列new LinkedBlockingQueue(); // 默认Integer.MAX_VALUE慎用几乎等于无限制任务堆积 → 内存暴涨 → OOM适合任务量可控、依赖方响应快的场景3.3 SynchronousQueue同步队列new SynchronousQueue();不存储任务每个插入必须等待一个移除来了任务必须有线程立刻接手适合任务执行快、不希望积压的场景选型建议场景推荐队列任务量可控、有上限ArrayBlockingQueue依赖方响应快、任务量小SynchronousQueue几乎所有场景避免用 LinkedBlockingQueue四、四种拒绝策略你用过几种当线程数和队列都满了新任务来了怎么办RejectedExecutionHandler handler策略行为适用场景AbortPolicy抛异常默认任务不可丢失CallerRunsPolicy由调用方线程执行限流、让上游感知压力DiscardPolicy直接丢弃无关紧要的任务DiscardOldestPolicy丢弃队列最老的任务优先处理新任务实战建议ThreadPoolExecutor pool new ThreadPoolExecutor( 10, 20, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue(100), new ThreadFactoryBuilder().setNameFormat(biz-pool-%d).build(), new RejectedExecutionHandler() { Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { // 记录日志或发告警 log.warn(任务被拒绝等待队列已满当前队列大小{}, executor.getQueue().size()); // 可选改为CallerRunsPolicy降级 if (!executor.isShutdown()) { r.run(); // 调用方线程执行 } } } );五、实战配置模板结合上面的分析给出几个实用配置5.1 CPU密集型任务int cpus Runtime.getRuntime().availableProcessors(); ThreadPoolExecutor cpuPool new ThreadPoolExecutor( cpus 1, // 核心线程数 cpus 1, // 最大线程数不需要太大 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(1024), // CPU密集型任务少可用无界队列但建议设上限 new ThreadFactoryBuilder().setNameFormat(cpu-pool-%d).build(), new AbortPolicy() );5.2 IO密集型任务推荐int cpus Runtime.getRuntime().availableProcessors(); ThreadPoolExecutor ioPool new ThreadPoolExecutor( cpus * 2, // 核心线程数 cpus * 4, // 最大线程数 60L, TimeUnit.SECONDS, // 空闲回收时间 new LinkedBlockingQueue(1024), // 建议设置上限防OOM new ThreadFactoryBuilder().setNameFormat(io-pool-%d).build(), new CallerRunsPolicy() // 队列满了降级处理不丢任务 );5.3 异步日志/消息发送场景ThreadPoolExecutor asyncPool new ThreadPoolExecutor( 2, 8, 30L, TimeUnit.SECONDS, new ArrayBlockingQueue(1000), new ThreadFactoryBuilder().setNameFormat(async-pool-%d).build(), new DiscardPolicy() // 日志丢了就丢了不影响主流程 );六、线上监控别忘了配置好了不监控等于没配。// 定时打印线程池状态 ScheduledExecutorService monitor Executors.newSingleThreadScheduledExecutor(); monitor.scheduleAtFixedRate(() - { ThreadPoolExecutor pool ioPool; log.info(线程池状态 - 核心:{}, 活跃:{}, 总数:{}, 队列:{}, 完成:{}, pool.getCorePoolSize(), pool.getActiveCount(), pool.getPoolSize(), pool.getQueue().size(), pool.getCompletedTaskCount()); }, 0, 30, TimeUnit.SECONDS);关键指标监控点getActiveCount()活跃线程数持续等于核心线程数 → 核心线程不够getQueue().size()队列长度持续增长 → 下游处理慢getCompletedTaskCount()完成任务数不增长但队列有任务 → 死锁或阻塞总结线程池配置记住三条铁律永远用 ThreadPoolExecutor 显式创建不要偷懒用 Executors队列必须设上限防止OOM根据任务类型配线程数CPU密集型和IO密集型天差地别如果你正在准备面试线程池的七大参数、工作流程、队列选型、拒绝策略是必问项。如果是线上问题监控活跃线程数和队列长度是最快的定位手段。没有银弹只有针对场景的最优解。 关注作者带你从源码角度理解Java底层原理在面试和实战中都能游刃有余。