「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战」
现象
先讲一下上周新鲜出炉的bug,业务反馈线上导出采购单功能超时,本来以为是业务导出采购单较多,让业务缩短下日期导出,结果还是导不出来,此时就怀疑是刚上线代码问题,立即进行了代码回滚,业务再重试导出成功。
本次上线代码主要是批量调用下游系统,改成了通过线程池调用,由于对下游系统不是强依赖,线程池调用设置的拒绝策略为丢弃策略(DiscardPolicy)
问题复现
测试代码:
1 | java复制代码public class DiscardTest { |
执行结果:一直卡着不动
原因分析
线程池线程数量到达3时,后续提交的任务执行丢弃策略(DiscardPolicy)。
我们看下DiscardPolicy实现如下:
1 | java复制代码/** |
执行丢弃策略时,丢弃策略的执行方法是什么都不做。 线程的状态依然是NEW。
要分析这个问题另外我们还需要看下线程池的submit方法里面做了什么,提交任务到线程池时,会包装成 FutureTask ,初始状态是 NEW。执行任务的是包装后的FutureTask对象。
1 | java复制代码/** |
我们看get()方法,FutureTask状态>COMPLETING 才会返回。因为拒绝策略没有修改FutureTask的状态,FutureTask的状态一直是NEW,所以不会返回,一直等待。
其他拒绝策略会不会导致阻塞
AbortPolicy是直接抛出异常,调用方马上可以获取结果
CallerRunsPolicy 是让主线程去执行,会更新任务状态
DiscardOldestPolicy 会poll出一个任务,但是没有任务处理,所以poll出来的任务是NEW状态
1 | java复制代码public static class AbortPolicy implements RejectedExecutionHandler { |
总结
- 使用Future.get(),需要根据业务实际情况设置超时时间
- 能不用丢弃策略(DiscardPolicy),就不要用,如确实需要用,则需要自己实现。
延伸,线程池使用还有哪些注意事项?
- 虽然使用CallerRunsPolicy不会造成卡死,但是还是要慎重,如果导致主线程被大量阻塞,对业务同样有影响。
- 线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。(阿里巴巴开发手册)
说明:Executors各个方法的弊端:
1)newFixedThreadPool和newSingleThreadExecutor:
主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
2)newCachedThreadPool和newScheduledThreadPool:
主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。
- 如果公司有全链路的trace(如阿里云的TracingAnalysis),线程中记得传递trace信息,不然trace信息会丢失。
本文已参与「新人创作礼」活动,一起开启掘金创作之路。
本文转载自: 掘金