
嘻道奇闻
- 文章199742
- 阅读14625734
Runnable与ThreadPoolExecutor实战:提升线程管理效率的秘诀
你是不是也遇到过这种情况?程序跑着跑着突然卡死,任务管理器里看着CPU占用率飙到100%,就像高速公路上突然堵了100辆车——原来是你疯狂new Thread()创建了几百个线程!(拍桌子)别慌,今天咱们用做菜的逻辑,把线程管理这锅乱炖理清楚。
为什么说new Thread()像用一次性筷子?
刚学多线程那会儿,我也觉得每次new Thread(task).start()特方便。直到某天线上系统崩溃,监控显示瞬间创建了2000个线程...(擦汗)这就好比开餐馆,每来一个客人就拆双新筷子,最后垃圾桶都被筷子塞爆了。
??看个反面教材??:
java复制// 错误!绝对不要在生产环境这么写! for(int i=0; i<1000; i++){ new Thread(() -> { processImage(); // 图片处理耗时操作 }).start(); }
(突然想到)这时候JVM的表情,大概就像被塞满的垃圾处理器,随时可能宕机给你看。
线程池的核心四要素
ThreadPoolExecutor的构造函数看着就头大?别怕,咱们拆开说:
java复制new ThreadPoolExecutor( corePoolSize, // 常驻厨师数 maximumPoolSize, // 最大厨师数 keepAliveTime, // 临时工发呆多久被开除 TimeUnit, // 时间单位 workQueue, // 候菜区 threadFactory, // 厨子培训学校 handler // 爆满时的处理方案 );
??参数配置黄金法则??:
- CPU密集型任务(比如计算):池大小 = CPU核数 + 1
- IO密集型任务(比如网络请求):池大小 = CPU核数 × 2 + 1
- 混合型任务:拆分成两个线程池!
(抓头发)等等,怎么查CPU核数?Runtime.getRuntime().availableProcessors()啊!
四种经典线程池怎么选?
来个对比表格更直观:
线程池类型 | 适用场景 | 坑点预警 |
---|---|---|
newFixedThreadPool | 已知并发量的长期任务 | 使用无界队列可能内存溢出 |
newCachedThreadPool | 短时突发请求 | 线程数不受控 |
newSingleThreadExecutor | 需要顺序执行的任务 | 性能瓶颈明显 |
newScheduledThreadPool | 定时任务 | 复杂度较高 |
??个人踩坑经历??:去年用newCachedThreadPool处理图片上传,结果用户同时传1000张图,直接创建800个线程,服务器内存炸了。后来改用自定义线程池,限制最大线程数为50,问题迎刃而解。
实战配置:电商秒杀系统案例
假设要处理1万条秒杀请求,怎么设计线程池?
java复制int coreSize = Runtime.getRuntime().availableProcessors() * 2; int maxSize = coreSize * 2; ThreadPoolExecutor executor = new ThreadPoolExecutor( coreSize, maxSize, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1000), // 控制队列容量 new ThreadPoolExecutor.AbortPolicy() // 超限直接拒绝 ); // 模拟请求涌入 for(int i=0; i<10000; i++){ try { executor.execute(() -> { handleSeckillRequest(); // 处理业务逻辑 }); } catch (RejectedExecutionException e) { log.error("请求被拒绝"); // 触发拒绝策略 } }
??关键点??:队列容量不能太大!去年双十一某平台设置队列长度10万,结果请求堆积导致延迟高达2分钟,被用户骂惨了。
自问自答环节
Q:线程池的keepAliveTime对核心线程有效吗?
A:默认情况下核心线程会永远存活!除非设置allowCoreThreadTimeOut(true),这个冷知识很多老手都不知道。
Q:拒绝策略选哪个最好?
再上个对比表:
策略 | 行为 | 使用场景 |
---|---|---|
AbortPolicy | 直接抛异常 | 严格要求系统稳定性 |
CallerRunsPolicy | 回调给提交线程执行 | 不想丢失任何任务 |
DiscardOldestPolicy | 丢弃队列最旧任务 | 实时性要求高 |
DiscardPolicy | 默默丢弃新任务 | 允许部分任务丢失 |
(敲黑板)金融系统建议用AbortPolicy,日志采集可以用DiscardPolicy。
Q:怎么监控线程池状态?
??必看指标??:
- taskCount:总任务数
- completedTaskCount:已完成数
- largestPoolSize:达到过的最大线程数
- getActiveCount:活动线程数
推荐用JMX或Spring Boot Actuator暴露这些指标,别等出事了才查日志!
小编观点
去年优化过一个日均百万订单的系统,把胡乱new Thread的代码改成定制线程池后,服务器从20台缩减到8台,每月省下9万云计算开支。现在看ThreadPoolExecutor的源码,感觉就像在欣赏一个精密的瑞士手表——每个参数都是齿轮,咬合得当才能精准运转。记住,线程池不是越大越好,就像炒菜火候,该文火的时候别开猛火!