专门想一下为什么线程池不用Excutors,之前的印象是错的,居然还拿来面试里讲,惭愧,这里暂时整理俩小问题,其他的后续可能会更新。。
线程池是创建的越大越好嘛
#线程池创建的越大越好吗
Tip:2024-04-10 更新
不是。
刚开始想着,线程池创建的大,请求来到不是刚好能分配线程执行这个请求嘛,不是挺好,为什么会导致效率更低呢?
其实不是,如果线程池创建太大,也就是说可用的线程很多,但是 CPU 的核数有限啊,线程执行需要调用 CPU ,所以多线程之间需要争夺 CPU 的执行权,这个过程中可能会发生频繁的上下文切换,导致效率更低。
并且呢,线程创建的多,也会消耗更多的内存空间给线程。所以可以根据上面的公式,设置好合理的线程参数,来最大程度的利用线程池。
那问题来啦,线程池创建的太小呢?
- 线程池创建太小,可能会导致任务来到的速度大于处理的速度,最终都堆积在任务队列中。假如使用的是无界的任务队列(比如 java 内置的 Executor 类的线程池),那就会导致 OOM 问题。
- 像 ThreadPoolExecutor 的线程池,它的任务队列是有限大小的,而且有相应的拒绝策略可以配置,更好的管理,防止 OOM 等问题。
如果是你,你会选用 Excutors 还是 ThreadPoolExecutor ,为什么?
这个面试会问到。
怎么选用得看它们的特性是什么,比如下面这点:
- 线程池的用途不同:
Excutors 类内部提供了很多静态方法,可以直接快速定义一些种类的简单线程池,比如 FixedThreadPool、cachedThreadPool 等的快捷创建方法。
如图:
再具体看其中一个方法:
可以看到这些静态方法,只是把 ThreadPoolExecutor 的参数改成了默认值,所以其底层也是用到了 ThreadPoolExecutor ,它们之间并不是完全不同的两个东西。
所以 Excutors 并不是之前想的,什么无界队列导致 OOM 溢出问题才不能用,而是因为这种方式生成的线程池不够"个性化",也就是不能满足一些复杂的需求。
- 至于 OOM 问题,那是因为一些线程池的特性,比如 SingleThreadPool 单线程池,它的任务队列真是无界的,所以真会溢出。
- 还有 CachedThreadPool ,它是基于内存的,所以不管你请求来多少,只要没有空闲线程它就会新创建线程来处理新请求,虽然这种场景确实存在,但是你的服务器不一定能撑得住啊。
最后,ThreadPoolExecutor 为什么优先用知道了吧,参数你自己定,更加的可控,自己指定线程创建工厂和任务队列,以及核心线程、最大线程等。
至于线程池参数怎么定那就是其他内容了,之前有整理过,不多说,解耦一下。
补充线程池参数配置:
合理设置线程池参数
我们调整线程池中的线程数量的最主要的目的是为了充分并合理地使用 CPU 和内存等资源,从而最大限度地提高程序的性能。在实际工作中,我们需要根据任务类型的不同选择对应的策略。
主要对于两种批量任务有不同的设置方法,最后会有一个公式:
- CPU 密集型任务
也就是大量需要进行计算、加密、解密等耗费 CPU 资源的任务。如果线程池的线程数量太多,会导致这些任务争夺 CPU 执行权,频繁的进行上下文切换,这个效率会降低。
所以一般设置线程数为 CPU 核数的 1 到 2 倍,我的项目里设置的是当前可用 CPU 核数的 1.5 倍。(最大线程数是 2 倍)
- IO 密集型任务
也就是需要进行文件读写,数据库读写等任务,不会耗费太多 CPU 资源,但是 CPU 会太闲,你闲下来不就是浪费资源了吗,反正机器不用休息(doge)
这种你就可以多开一些线程,在执行 IO 任务的时候,CPU 不用等待 IO 任务结束,而是先去执行一些需要 CPU 的操作,所以要多开线程,去调用 CPU,尽可能的提高 CPU 利用率。
- 通用计算公式
java 并发编程里有这个公式:线程数 = CPU 核心数 *(1+平均等待时间/平均工作时间)
其实也对应两种类型的任务,比如平均等待时间长,就是 IO 密集型任务,要多开线程提高 CPU 利用率。相反,工作时间长就是 CPU 密集型,需要适量设置线程数。