事先声明
题目并没有哗众取宠的意思,我确实有在用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、合理利用线程池
觉得有用,点个关注,喜欢请关注。
同名公众号【码农小麦】