四种拒绝策略,各有各的哲学
AbortPolicy——默认,直接抛 RejectedExecutionException,让提交方知道「满了」,配合 try-catch 做降级;CallerRunsPolicy——谁提交谁自己跑:提交线程被占住干活,上游自然就慢了,相当于内置反压,适合允许慢但不许丢的任务;DiscardPolicy——静默丢弃,最危险,丢了你都不知道;DiscardOldestPolicy——丢队列里最老的腾位,适合「新数据比旧数据有价值」的场景,比如行情推送。选型一句话:宁可吵着失败,不要安静地丢——抛异常至少留下排查线索。
参数怎么定:公式是起点,压测是终点
经典公式:CPU 密集型任务,线程数约等于核数 + 1(补位防偶发阻塞);IO 密集型,线程数约等于核数 × (1 + 等待时间/计算时间)。公式给的是起点——真实系统的等待比、上下文切换成本,公式里都没有,最终以压测为准。另外队列大小要和拒绝策略联动:队列太深,堆积的任务等到处理时可能早已超时,白占内存——某些场景「先拒掉」比「先存着」更负责。
监控:线程池必须可观测
线程池自带关键指标,接入监控定期采集:getActiveCount() 活跃线程数、getQueue().size() 积压任务数、getCompletedTaskCount() 累计完成数。积压持续上涨就是容量告急的前兆——比 OOM 早上好几个小时。
动态调参:不用重启
ThreadPoolExecutor 提供运行时 setter:setCorePoolSize、setMaximumPoolSize 实时生效——新值大于旧值会预创建线程补位,小于则多余空闲线程按超时回收。把它和配置中心串起来,就是「大促调大、平时调回」的动态线程池,美团的技术实践文章讲得很细。核心公式化记忆:参数外置 + 监控 + 运行时 setter。
关闭:温和与粗暴
shutdown() 温和:不再收新任务,已提交的跑完才散伙;shutdownNow() 粗暴:立刻中断所有线程,返回还没来得及跑的任务列表给你善后。线程池不用了必须关——池里的线程默认非守护,只要有一个池没关,JVM 就退不出去。应用里通常有容器生命周期托管,但写工具类、跑批脚本时,这个坑特别常见。
线程池篇完。下一篇并发容器第一主角 ConcurrentHashMap——从 JDK 7 的分段锁到 JDK 8 的 CAS 加 synchronized,看锁的粒度怎么一步步做细。
评论 (0)