先说下我们项目组在使用线程池时踩到的坑:
- 线程池的参数设置一定要结合具体的业务场景,区分I/O密集和CPU密集,如果是I/O密集型业务,核心线程数,workQueue等待队列,最大线程数等参数设置不合理不仅不能发挥线程池的作用,反而会影响现有业务
- 等待队列workQueue填满后,新创建的线程会优先处理新请求进来的任务,而不是去处理队列里的任务,队列里的任务只能等核心线程数忙完了才能被执行。有可能造成队列里的任务长时间等待,导致队列积压,尤其是I/O密集场景
- 如果需要得到线程池里的线程执行结果,使用future的方式,拒绝策略不能使用DiscardPolicy,这种丢弃策略虽然不执行子线程的任务,但是还是会返回future对象(其实在这种情况下我们已经不需要线程池返回的结果了),然后后续代码即使判断了future!=null也没用,这样的话还是会走到future.get()方法,如果get方法没有设置超时时间会导致一直阻塞下去!
伪代码如下:
// 如果线程池已满,新的请求会直接执行拒绝策略
Future<String> future = executor.submit(() -> {
// 业务逻辑,比如调用第三方接口等耗时操作放在线程池里执行
return result;
});
// 主流程调用逻辑
if(future != null) // 如果拒绝策略设置不合理还是会走到下面代码
future.get(超时时间); // 调用方阻塞等待结果返回,直到超时
但这不是根本原因,future是线程池返回的,伪代码如下:
Future<String> future = executor.submit(() -> {
// 业务逻辑,比如调用第三方接口等耗时操作放在线程池里执行
return result;
});
三. 改进
- 调整线程池参数,核心线程数基于线上接口的QPS计算,最大线程数参考线上tomcat的最大线程数配置,能够cover住高峰流量,队列设置的尽量小,避免造成任务挤压。关于线程数如何设置会在后续文章中单独讲解。
- 扩展线程池,封装原生JDK线程池ThreadPoolExecutor,增加对线程池各项指标的监控,包括线程池运行状态、核心线程数、最大线程数、任务等待数、已完成任务数、线程池异常关闭等信息,便于实时监控和定位问题。
- 重写线程池拒绝策略,主要也是记录超出线程池负载情况下的各项指标情况,以及调用线程的堆栈信息,便于排查分析,通过抛出异常方式中断执行,避免引用的future不为null的问题。
- 合理调整future.get超时时间,防止阻塞主线程时间过长。