个人博客地址:sillybaka的博客
在面试中被问到了这个问题:
使用过线程池吗?有什么参数?怎么设置参数?为什么要这样设置?具体场景呢?
—— 当时的我不怎么会哈哈所以作出以下总结
常用的构造方法
public ThreadPoolExecutor(int corePoolSize
, // 核心线程数
int maximumPoolSize
, // 线程池中最大线程数
long keepAliveTime
, // 空闲线程保持存活的时间
TimeUnit unit,
BlockingQueue<Runnable> workQueue
// 核心线程的任务队列
)
1、corePoolSize
,核心线程数 --> 线程池中最主要的线程,用于处理任务队列中的任务
2、maximumPoolSize
, 线程池中最大线程数 --> 包含了核心线程+普通线程,不能超过最大线程数
3、keepAliveTime
, 空闲线程保持存活的时间 --> 空闲普通线程最长能够存活的时间,因为如果普通线程空闲,就说明核心线程就已经足够处理所有任务
4、workQueue
,线程的任务队列 --> 每个线程都线程监听该队列,暂时无法处理的任务会存放在该阻塞队列中
线程池的工作流程
源码分析 —— ThreadPoolExecutor#execute()
—— 向线程池提交任务
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 线程池状态的全局变量 包含 线程池是否运行 + 线程数量 ,用位运算进行状态压缩
int c = ctl.get();
// 1、线程数是否小于核心线程数
if (workerCountOf(c) < corePoolSize) {
// 2、尝试创建新的核心线程,并将任务分配给核心线程
if (addWorker(command, true))
return;
// 重试获取线程池状态
c = ctl.get();
}
// 3、如果线程池仍在run,并且任务队列未满(能够添加任务成功)
if (isRunning(c) && workQueue.offer(command)) {
// 这里双重校验看线程池是否被关掉
int recheck = ctl.get();
// 如果线程池被关掉了,就移除该任务
if (! isRunning(recheck) && remove(command))
// 并且交给拒绝策略来处理
reject(command);
// 这个分支应该是创建核心线程失败之后,并且线程池中没有线程时 才会到达,创建一个空的普通线程
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 4、任务队列满了 --> 看能否创建普通线程处理这个任务(即看看 线程数是否已满)
else if (!addWorker(command, false))
// 线程数已满 则交给拒绝策略来处理
reject(command);
}
流程图
- 如果当前正在运行的线程数
小于corePoolSize
,则创建新的线程并分配任务给它(这些线程会监听workQueue) - 如果线程数
大于等于corePoolSize,并且任务队列workQueue未满
,则将任务放入任务队列 - 如果
workQueue满了
,并且线程池线程数小于maximumPoolSize
,则创建新的线程去做这个任务 - 否则,就将任务交给拒绝策略处理器
那么,keepAliveTime用在了哪里呢?是如何判定一个线程空闲时间超过了这个时间呢?
在内部类**Worker类(即线程池中的线程)**中我们可以看到它的run方法(实现Runnable的接口)
再往下看,就是具体的线程执行流程
再看看 getTask() 的实际逻辑
- 线程池中的每一个线程(Worker)在run了之后,就会不断地从任务队列中获取任务
- 如果
线程数大于corePoolSzie
,并且某线程超过了keepAliveTime没有获取到任务,就会再次判断任务队列是否为空,如果任务队列仍为空,该线程就会被清除(空闲线程 + 超过keepAliveTime没有任务做)
参数设置
线程池中的线程数需要根据实际的使用场景来设置,根据不同系统的特性又可以分为: 1、CPU密集型 ; 2、IO密集型
CPU密集型
cpu密集型,那就是使用cpu的时间会占绝大多数,只有少量的时间会用来IO,所以很少会有线程因为IO而阻塞,所以线程数可以适当的少一点(每一个线程都一直执行,让不出cpu资源,更多也没用)
所以一般的核心线程数corePoolSize会设置为:
计算机的CPU核数+1(因为最多只能同时并发运行cpu的核数那么多个线程,+1是考虑到那少部分因为IO而阻塞的线程)
IO密集型
io密集型,那就是IO的时间会占大多数,线程的大部分时间都处于阻塞态当中,只有少部分的时间会使用到cpu,所以线程数应该多一点(因为每个线程大部分时间都在阻塞,所以就需要其他非阻塞线程来使用cpu资源)
所以一般的核心线程数corePoolSize会设置为: cpu的核数 * 2