这个问题对于看过线程池源码的同学应该已经知道答案了,没有看过源码的也不要慌,现在我们就一起看一下线程池线程的保活策略。
一、线程池中在哪执行任务
首先进入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
方法实现阻塞直到获取到任务。
当线程异常之后,通过新增线程的方式实现线程的补救,保证线程池的运行。