阿里规约手动创建线程池,我为什么还继续使用Executors呢?

事先声明

题目并没有哗众取宠的意思,我确实有在用Executors创建线程池。本文也不会赘述有关线程池参数,线程状态等概念。

尽管我知道阿里规约手动创建线程池背后的深意,通过ThreadPoolExecutor创建线程池可以让使用者更加明确线程池的运行规则,规避资源耗尽的风险。

资源耗尽的风险

资源耗尽的风险主要还是源于内存:

1、使用Executors.newFixedThreadPool和Executors.newSingleThreadExecutor创建的线程池,等待队列默认值是Integer.MAX_VALUE,基本等同于无界的队列。

毋庸置疑,线程数肯定是有限的,尤其对于密集计算型任务,线程数设置太大只会徒增线程消耗,并不会提高任务执行效率。如果请求量过大,大量的请求数据堆积在队列中,极有可能发生OOM。

2、使用Executors.newCachedThreadPool和Executors.newSingleThreadScheduledExecutor创建的线程池,最大线程数是Integer.MAX_VALUE,基本等同于无限大。

对于大量的请求,无限的创建线程,ThreadStackSize按1024k算,极有可能发生OOM。

为什么我使用Executors创建线程池

原因有两个:

1、不喜欢各类的条条框框,Java包括Java生态圈本身已经有很多规约了,还需要阿里再来一套规约?

2、Java既然自带了这样的线程池工具,应该有用武之地。

我个人看来,使用Executors和直接使用ThreadPoolExecutor并没有本质区别,更多的关注还应该放在任务本身。

假设使用Executors创建的线程池会出现OOM,那么采用ThreadPoolExecutor就能规避吗?答案大概率是否定的。

几点使用线程池的考虑

我通常会先考虑以下几点:

1、任务偏IO还是偏计算

2、通常程序线程池用来处理小而多批量任务

3、线程数量和CPU核数相呼应

4、请求量多少和请求量大小

5、定时监控线程池当前运行状态

6、线程数量可以动态设置

7、适当的控制线程数

8、任务耗时的话尽量去拆分或优化

场景假设

先来看个场景,假设A会持续向B推送数据,量级为1亿,payload为100B;B收到数据后进行应答后,A会继续向B推送;为了A不影响数据推送,B会直接应答,然后线程池异步处理A推送来的数据。

可能假设场景有些鸡肋,但现实也不乏这种场景,将就着看吧。

假设使用Executors.newFixedThreadPool创建线程数为10的线程池,单个线程执行一次任务耗时10ms,想想看,5min中后,线程池处理了10100300个任务,忽略线程切换带来的损耗,累计处理30w个任务。

剩下还有约1亿的数据堆积到了队列中。100B*100000000/1024,大约9g的数据,绝对OOM了。

问题的根本原因

那问题来了,使用ThreadPoolExecutor的话我应该开多少个线程?队列应该设置多大?数据来了线程满了,队列满了,直接丢弃数据?还是业务线程本身继续执行?

这个时候线程基本已经忙不过来了,CPU狂飙,内存OOM。

分析现状解决问题

显然这个时候需要把速度降下来,而不是无脑提交到线程池中。

我们对照上面几点重新优化下这个程序流程:

1、任务是偏计算型的,4核开10个线程还算合理,可以动态设置线程数再进行调试

2、请求量总大小大约10g,对于8g内存的应用,考虑占用1g内存,大约10000个任务数据

3、使用Executors.newSingleThreadScheduledExecutor定时监控线程池的状态

针对上面的无脑分析

1、我们使用Executors.newFixedThreadPool和Executors.newSingleThreadScheduledExecutor,前者用来执行任务,后者用来定时输出线程池状态

2、B端协商A端,每次推送1000个数据,10个线程处理10000个,使用信号量严格控制,数据不进队列;亦或使用信号量控制,队列至多进入10000个数据;信号量大小为10000+线程数

3、动态设置线程池最大线程数,定时监控线程池状态,没什么神奇,线程池提供了这样的方法

总结

以上就是本文所要阐述的观点,主要想表达以下几点:

1、任何规约不要盲从

2、尽可能多地关注自己的业务数据

3、合理利用线程池

觉得有用,点个关注,喜欢请关注。

同名公众号【码农小麦】

猜你喜欢

转载自blog.csdn.net/weixin_43275277/article/details/125857510