面试时经常被问的一个问题,线程池中线程是如何保活的?
这个问题对于看过线程池源码的同学应该已经知道答案了,没有看过源码的也不要慌,现在我们就一起看一下线程池线程的保活策略。
一、线程池中在哪执行任务
首先进入ThreadPoolExecutor的execute方法。
- 首先检查当前工作线程数量,workerCountOf(c) 是否小于核心线程数量corePoolSize,如果小于就创建一个新线程addWorker()执行这个任务。
- 如果线程池在运行且任务加入队列成功,但是当前工作线程数量为0,也会创建一个新线程addWorker()。
- 如果任务不能被加入任务队列(workQueue.offer(command)返回false), 也会创建一个新线程addWorker()。
在execute方法内部有三处调用addWorker()方法的位置,进入到addWorker() 方法中,可以看到有这样一行代码new Worker(firstTask) 。
而 Worker类又实现了 Runnable,所以线程池中的线程也就是Worker,而执行就在run()方法内部,我们只需要找到Worker类中的run方法即可。
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
run 方法内部调用了 runWorker,秘密就在runWorker方法内部。
所以线程池中线程最终就在runWorker方法中执行的。
二、getTask 获取任务方法
在上面ThreadPoolExecutor的execute方法中,有一步是往阻塞队列中放入任务(workQueue.offer(command))。
上面我们找到了线程池中线程执行任务的地方,那么我们看看是从哪读取的任务?
runWorker方法中有一行代码task = getTask(),此处就是从阻塞队列中获取任务的代码,让我们看一下 getTask() 的内部实现。
核心代码就是
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
根据timed 的状态判断,当工作线程数量大于核心线程数量时调用poll方法获取任务,当工作线程数量小于和核心线程数量时调用take方法。
对于阻塞队列的介绍如下:
- poll 方法如果队列不为空,返回头部元素。如果队列为空会将线程阻塞在此处,阻塞时间是keepAliveTime。当时间到了获取不到任务时返回null。
- take方法如果队列不为空返回头部元素。如果队列为空会将线程阻塞在此处,直到队列中有元素可供使用。
对于非核心线程,当线程池中的线程数量超过核心线程数量且空闲时间超过keepAliveTime时,非核心线程会被回收。
通过这种方式,线程在没有任务时就阻塞在队列上,从而实现保活。
上面我们知道了非核心线程的保活策略,那么对于核心线程又是如何保活的呢?
三、核心线程如何实现的保活
在线程池中核心线程默认是不会被回收的。
不过我们可以通过设置allowCoreThreadTimeOut来实现,getTask方法中timed的校验。
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
所以当allowCoreThreadTimeOut 设置为true时,核心线程在没有任务可以执行时会使用take方法进行阻塞,直到获取到任务位置,而不是因为没有任务就被销毁,从而实现的线程保活。
任务执行的过程是无法保证不出问题的,不管是核心线程还是非核心线程,当线程中出现异常之后,线程池是如何处理的呢?
四、线程异常之后如何保活
继续回到runWorker方法中,其中task.run()是真正执行任务的地方,当此处发生异常之后,有try catch。
所以当任务执行异常之后,会依次执三个finally块中的代码。
再看processWorkerExit() 方法之前,我们先看一个参数completedAbruptly。
completedAbruptly 代表是否是异常退出的,默认是true代表异常退出。
在runWorker方法中,如果线程任务是正常执行完成的,会在最后修改该值。
由于我们是异常线程,所以代码肯定不会走到这,直接走到finally中的processWorkerExit方法。
在processWorkerExit方法中,它会根据completedAbruptly的值来调整线程池中的工作线程数量,从工作线程集合中移除该线程,并根据线程池的状态和工作线程数量决定是否需要添加新线程。
!completedAbruptly 判断工作线程是不是异常退出的,如果不是异常退出的计算最小线程数量。
如果允许核心线程回收allowCoreThreadTimeOut=true,min为0。
如果min为0 且工作队列不为空! workQueue.isEmpty(),min为1。
如果当前工作线程workerCountOf(c)大于等于这个最小的线程数量min,直接返回。
如果小于这个最小的工作线程数量min,调用addWorker。
此处addWorker 的触发条件就是当线程池的状态小于STOP 也就是线程池还在运行runStateLessThan(c, STOP)时且不满足上述不需要添加新线程的判断。
当上述条件满足的时候,则调用addWorker(null,false)添加一个新的工作线程,因为传入的参数Runnable为null,所以这个新线程会从任务队列中继续读取任务来执行。
最后总结一下,当线程异常之后,按照正常情况来说线程就直接消失了,但是通过processWorkerExit方法的补救,增加了一个新的线程,保证线程池的运行。
五、总结
线程池中的线程分为核心线程与非核心线程。
核心线程默认不回收,可以通过设置allowCoreThreadTimeOut为true 来回收。
非核心线程在获取任务为空且空闲时间超过一定时间之后进行回收。
线程池的保活策略通过阻塞队列的阻塞特性实现,poll 方法实现可以指定超时时间的阻塞,take 方法实现阻塞直到获取到任务。
当线程异常之后,通过新增线程的方式实现线程的补救,保证线程池的运行。