欢迎关注公众号(通过文章导读关注:【11来了】),及时收到 AI 前沿项目工具及新技术的推送!
在我后台回复 「资料」 可领取
编程高频电子书
!
在我后台回复「面试」可领取硬核面试笔记
!文章导读地址:点击查看文章导读!
感谢你的关注!
技术杂谈
关于线程池在生产环境中的使用
这里整理了一些线程池在生产环境中使用的建议来帮助我们更好的在项目中使用线程池
一个项目使用一个线程池还是多个线程池?
一般建议是不同的业务使用不同的线程池,从而避免非核心业务对于核心业务的影响
如果所有的业务使用同一个线程池,非核心业务可能执行速度很慢,从而占用了很多线程迟迟不归还,导致核心业务在任务队列中等待,拿不到线程执行
并且还可能造成 死锁问题
,当父子任务使用同一个线程池时,父任务如果将核心线程全部占用之后,等待子任务完成,由于核心线程没有空闲的,导致子任务进入到任务队列中等待线程资源,导致父子任务之间互相等待
线程池在 RocketMQ 中的使用
在 MQ 中使用了很多线程池,这里说一下在发送消息时使用的线程池:
1、任务队列:创建了 异步发送者线程池
,任务队列
使用长度为 50000 的阻塞队列
2、线程数:核心线程数
和 最大线程数
相同,为 CPU 核数
3、存活时间:非核心线程存活时间
60s
4、线程名称:重写了线程工厂,主要是 为了线程的命名规范
,这样在查询日志时,只要做好业务之间的隔离,就可以很容易的根据线程名称来定位到对应的业务,便于分析线上问题
private final ExecutorService defaultAsyncSenderExecutor;private final BlockingQueue<Runnable> asyncSenderThreadPoolQueue;this.asyncSenderThreadPoolQueue = new LinkedBlockingQueue<Runnable>(50000);this.defaultAsyncSenderExecutor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),Runtime.getRuntime().availableProcessors(),1000 * 60,TimeUnit.MILLISECONDS,this.asyncSenderThreadPoolQueue,new ThreadFactory() {private AtomicInteger threadIndex = new AtomicInteger(0);@Overridepublic Thread newThread(Runnable r) {return new Thread(r, "AsyncSenderExecutor_" + this.threadIndex.incrementAndGet());}});
那么我们在自己的项目中使用的线程池就可以参考 MQ 中的用法,更加规范的使用线程池
至于为什么要这样设置核心线程数,一方面是参考了设置核心线程数的经验(CPU 密集型的任务令线程数等于 CPU 核心数,减少了线程之间的上下文切换,速度比较快),另一方面 RocketMQ 肯定内部经过性能测试,发现这样设置性能比较好一些
关于线程数量的设置
这里参考了美团技术团队中的《Java线程池实现原理及其在美团业务中的实践》
对于线程池的线程数量配置,不同场景下的需求不同,因此对应的数量配置也不同
一般使用线程池的场景无非就两种:
及时性任务
:需要迅速完成,降低用户等待时间非及时性任务
:批量完成任务,一般是后台任务
那么对于 及时性任务
来说,需要尽可能快的完成任务,因此要 尽可能增大可执行任务的线程数量
,来尽可能快的完成任务,不要设置任务队列
,因为只有任务队列满了之后,才会去创建非核心线程执行
对于 非及时性任务
来说,这类人物并不面向用户,特征是任务量很大,需要批量处理,不需要很低的延迟,因此需要设置合适线程数量, 利用有限的资源去尽可能快的执行任务
,并且设置任务队列去缓冲任务,但是尽量不要使用无界的任务队列,无界队列任务堆积过多会造成 OOM
因此可以发现,在不同场景下,我们可以对参数的配置有一个大概的方向,但是具体配置多少还没有一个通用的公式 ,因此美团技术团队设计了 动态化线程池
,提供了对线程池的监控以及参数动态调整,这样在调整参数之后,通过监控可以看到整个线程池的负载情况,可以选出比较合适的参数方案
线程池负载的定义
线程池的负载可以根据活跃的线程数和最大线程数的比值来反映
线程池活跃度 = activeCount/maximumPoolSize
,当活跃度升高,代表着线程池负载在逐步上升
还可以 从任务队列中等待的任务数量
或者 发生拒绝策略的次数
来反映
自定义拒绝策略
在线程池中可以 自己去定义拒绝策略
,如果线程池无法处理更多的任务了,可以在自定义的拒绝策略中,将拒绝的任务 异步持久化
到磁盘中去,之后再通过一个后台线程去定时扫描这些被拒绝的任务,慢慢执行
保证严格的任务不丢失:如果线上机器突然宕机,线程池的阻塞队列中的请求怎么办?
如果宕机,重启之后,线程池阻塞队列中的任务就会全部丢失
如果想要解决这种情况的话,有这么一个 解决方案
:在将任务提交到线程池中去的时候,先把任务在数据库中存储一份,并记录任务执行的状态:未提交、已提交、已完成,执行完之后的话,将任务状态标记为 已完成,如果宕机后,导致任务丢失,就可以去数据库中扫描任务,重新提交给线程池执行
阿里手册中的线程池规范
在使用线程池的时候,需要注意一些规范,以免出现不必要的问题,可以参考阿里巴巴 Java 开发手册,如下:
线程池名称命名规范:
线程池创建规范: