上次分享了java线程池的原理,这次对各种线程池从内存和cpu使用上进行分析,让读者清楚以后线程池的使用原理
和在实际项目中如何使用线程池和怎么选取阻塞队列
1.线程池不允许使用
Executors
去创建,而是通过
ThreadPoolExecutor
的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:
(1)使用newFixedThreadPool内存模型
![](https://i-blog.csdnimg.cn/blog_migrate/16b6b481904562b7a3aee348cb4aa660.png)
(2)SingleThreadExecutor 内存模型
![](https://i-blog.csdnimg.cn/blog_migrate/d69b96406c8593481d68784f23ec0c5a.png)
结论:这两种线程池在有大量任务到来的时候,就会出现OOM,通过上面的内存申请图形可以看到,根本原因在于,这两种线程池内存采用了LinkedBlockingQueue,
而LinkedBlockingQueue的默认打下是2的32次方,是一个无穷大的数字,所以就会有大量的任务堆积在内存里面,造成内存泄漏,关于线程池的核心线程,最大线程数量,阻塞队列
线程工厂和拒绝策略可以看之前我的分享文章
在采用
如果采用LinkedBlockingQueue 必须设置大小,不然相连线程池塞入过快,会内存溢出
2.采用newCachedThreadPool内存
(1): newCachedThreadPool 采用的是SynchronousQueue这种阻塞队列,特具有的特性是;SynchronousQueue 也是一个队列来的,但它的特别之处在于它内部没有容器,一个生产线程,当它生产产品(即put的时候),如果当前没有人想要消费产品(即当前没有线程执行take),此生产线程必须阻塞,等待一个消费线程调用take操作,take操作将会唤醒该生产线程,同时消费线程会获取生产线程的产品(即数据传递),这样的一个过程称为一次配对过程(当然也可以先take后put,原理是一样的)。可以认为这是一种线程与线程间一对一传递消息的模型。
采用这种线程池如果任务能够快速执行完的话,那就不会有大量的任务推挤在线程池里面,隐藏也就不会造成OOM,但是,但是哈,我们使用线程的目的就是有时候要做一些耗时的操作,所以采用这种线程池的时候,再有大量任务到来的时候,也不安全,会造成CPU处于繁忙状态,那么手机就会发烫,发热,电池不耐用问题
(2):newScheduledThreadPool内存模型:
![](https://i-blog.csdnimg.cn/blog_migrate/31cef12cc9c3a66de3766fe216c83ef2.png)
采用newScheduledThreadPool是特别危险的,在有大量任务到来的时候,不但会造成OOM,而且还会造成CPU占用率特别高,newScheduledThreadPool 采用的是DelayedWorkQueue 队列,
无界延迟阻塞队列DelayedWorkQueue
采用自定义线程池的时候,要注意两点:
如要使用LinkedBlockingQueue的时候,必须要指定阻塞队列大小,或者采用SynchronousQueue,这个队列不需要指定大小,因为他不是一个容器
下面这个demo 是 例子:
ExecutorService fixedThreadPool = new ThreadPoolExecutor(2, 3, 100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100),
new ThreadFactory() {
@Override
public Thread newThread(@NonNull Runnable r) {
Thread t = new Thread(r);
t.setName("自定义线程");
return t;
}
}, new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
}
});
下面的截图是运行起来以后内存和cpu使用情况
![](https://i-blog.csdnimg.cn/blog_migrate/4466c813c72978ad132c76b5779e50e0.png)
总结:我们平时在使用线程池的时候,要根据具体的业务,分类别的处理定义线程池,不要一股脑上来就使用原生的线程池,在
请求任务不多的情况下,可能不会有什么问题,但是用户量大,并发的情况下,就会有各种个样子的问题,因此在定义线程池的时候,每一个线程要定义前缀,方便后续问题排查,同时还要定义好一些解决策略。