JUC 线程池(二)实战与细节

110 阅读4分钟

这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战

引言

昨天我们讲了线程池是什么,我们为什么要使用线程池,以及线程池的7个参数详解,今天我们就来实战一下,讲讲线程池具体是如何使用的,提交任务的execute()方法和submit()方法有什么区别,如何拿到submit()方法的返回值,以及线程池使用过程中我们需要注意的一些细节。

很多情况下我们看一个东西觉得很简单,但要你实际上手的时候却发现并不容易。毛主席的对策就是:实践。因为实践才能真正的发现问题,继而我们去解决问题,这一过程中我们总结出来的东西才是我们最大的财富。

实战

image.png 虽然看起来非常简单,但是在实际场景中有一个重要的细节要注意:核心线程数和最大线程数如何设置,用什么作为参考依据?

核心线程数设置

我们一般会根据执行任务的类型来确定核心线程数的大小。

1.CPU密集型,即任务都是使用CPU进行计算,加密解密,视频解析等,我们可以将核心线程数设置为机器的CPU的核心数

image.png 注:使用 Runtime.getRuntime().availableProcessors()获取CPU核心线程数。

2.IO密集型,这里的IO是广义的IO不仅指磁盘读写,也指网络IO,例如数据库连接,三方系统调用等,这种情况下CPU的利用效率没有上一种高,我们可以将其设置为2倍的CPU核心线程数。比如Netty的线程池便是使用了 2 * N。

3.混合型,既有网络请求,或数据库请求,又有数据处理的部分,我们称之为混合型的任务。这种情况下我们一般都是靠经验了,设置一个50、80、100然后上线看一下服务器的压力,做出调整。当然也有一个计算公式:((线程等待时间 / 线程CPU实际平均运行时间 + 1 )* CPU核心数 )。下面举一个例子:比如在Web服务器处理HTTP请求时,假设平均线程CPU运行时间为100毫秒,而线程等待时间(比如包括DB操作、RPC操作、缓存操作等)为900毫秒,如果CPU核数为8,那么根据上面这个公式,估算如下:((900 / 100 + 1)* 8)= 80。

很多时候看到别人用的线程池,设置的核心线程数和最大线程数相等,这是为什么?

这一点上一节我们大概说过,这里我们举个例子,假设 coresize设置的是2,maxsize设置的是80,任务队列设置的是1000,那么当2个任务进来的时候,线程池开启了2个线程来处理任务,但当任务队列有800个队列时,还是只有2个线程在处理任务,只有当任务队列满了的时候才会开启新线程(共20个),这种情况时非常不合理的。所以我们一般的做法是将核心线程与最大线程数设置为相同,避免这种尴尬的事情发生。

说一个之前生产上遇到的例子:

业务背景:当一个指令下发后要同时给2000个执行人员发送微信公众号消息。但是客户反馈有的人十多分钟才收到消息,有人二十多分钟才收到消息。

排查到最后就是线程池参数设置的问题:开发这块业务的同事不太了解线程池运行的原理,三个关键参数设置的分别是 核心线程数:2,最大线程数:10,任务队列容量:1w。在线程数没有达到1万之前,只有两个线程在干活,怪不得要半小时才发完消息。查询用户信息消息需要300ms,发送微信公众号需要调用微信的接口500ms-1000ms左右,经过几轮测试之后最后我们将参数为 300,300,1w。2000条数据一分多钟搞定。

真的是吃了没文化的亏。

todo 
### 定时执行的线程池的原理是如何实现的?
### 非核心线程过期销毁是如何实现的?
### 任务队列有哪些容器?