线程池里的线程是如何进行回收的?
首先我们知道线程里是分"核心线程"和"非核心线程的",并且非核心线程数 = 最大线程数 - 核心线程数。
非核心线程在空闲了AliveTime之后,就会被线程池给回收,销毁。
那么核心线程数会不会被销毁呢?答案是可能会。
如果你给线程池的属性allowCoreThreadTimeOut设为true,那么核心线程其实就和非核心线程一样,空闲一段时间后也会被回收。
在具体看源码实现时,我们还需要知道”线程“是如何在线程池里面体现的。
我们可以看到ThreadPoolExecutor类里面有一个内部类Worker。
Worker
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
/** Thread this worker is running in. Null if factory fails. */
final Thread thread;
/** Initial task to run. Possibly null. */
Runnable firstTask;
/** Per-thread task counter */
volatile long completedTasks;
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);
}
//....略
}
- 可以发现Worker是继承了AQS的,是具有加锁功能的。
- 实现了Runnable类。
- 并且里面内置了一个Thread线程类成员属性,这个属性的初始化是通过Worker构造函数里面初始化的,具体是通过线程工厂ThreadFactory().newThread(this)创建的,并且可以发现创建时将this即Worker传进去了,而Worker又是实现了Runnable方法,本身可以看作一个任务。
- 重写了run方法。
那么到底是谁?这个线程到底是哪个线程呢?
我们可以看看addWorker方法,可以理解为创建线程方法:
创建Worker
private boolean addWorker(Runnable firstTask, boolean core) {
//....部分代码略
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
可以看到首先new了一个Worker对象,并且取出Worker对象里面的Thread属性。
然后加上mainLock锁(至于为什么这里要加锁,后面会说),将Worker对象放入workers中(存储worker的HashSet集合),然后把mainLock锁释放。重点来了,然后调用Worker对象里的那个Thread.start(),开启线程了,所以真正执行任务的就是Worker类的Thread线程属性。那么具体工作任务,就是Worker类的run()方法,因为之前说了,创建Thread属性的时候,是将Worker类(实现了Runnable类)作为参数传给Thread的构造函数了,所以可以这样理解:Worker承载了Thread,而Worker又作为任务和Thread进行绑定。
让我们看看Worker具体执行了什么任务,即run()方法:
public void run() {
runWorker(this);
}
继续进去里面的runWorker方法
Worker的具体任务
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
可以发现这个任务一直处于一个while循环。一直尝试从任务队列(真正存放用户提交的任务的队列)里获取任务,如果获取得到任务就进入循环,然后又是加锁操作"w.lock()",(因为Worker实现了AQS,可以直接加锁,至于为什么加锁,后面讲),然后执行task.run(),即拿到任务队列里的任务(Runnable)进行执行。执行结束释放锁w.unlock(),继续回到while循环,继续尝试从任务队列获取任务。
我们再继续看看从任务队列里获取任务的getTask()方法:
从任务队列获取任务
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
首先会获取线程池的状态,然后判断是否处于ShutDown状态,并且任务队列里是否为空,可以发现即使目前线程状态处于ShutDown,但是任务队列仍然存有任务,那么这个线程也还是会去拿任务,继续执行。
\
然后里面有一个很关键的标志判断:
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
如果允许核心线程超时回收,或者目前线程数>核心线程数
那么这个timed就为true.则去任务队列里获取任务的方式是poll,否则是take().
poll:有时限的阻塞等待,时限就是我们定义的keepAliveTime,如果超过这个时限没有获取到任务,则返回Null,那么之前线程的while循环就会退出,就会进入线程销毁方法,完成线程的回收。
take:无时限的阻塞等待,就一直死等,不会退出while循环。
总结:所以线程池并没有具体指定某个线程就是核心线程、或者非核心线程。是否需要销毁,是根据当时线程池里的状态来决定。
线程池里创建线程之前为什么要加MainLock锁?
首先我们可以注意到 MainLock是线程池ThreadPoolExecutor的一个成员属性。
并且线程池的ShutDown()方法,即销毁线程池方法里,直接使用MainLock锁:
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
SHUTDOWN状态时,不允许创建新的线程,也不允许加入新的任务,老的任务只会由老的线程去执行掉。
所以通过mainLock加锁来保证两者的互斥性。
Worker在执行真正task任务之前,为什么需要加锁?
这个问题同样与线程池执行SHUTDOWN方法有关:
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
可以看到销毁线程池里执行了interruptIdleWorkers方法,即打断空闲线程。
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
遍历每一个worker,并且拿到对应的线程
关键在于这句判断:
if (!t.isInterrupted() && w.tryLock())
如果线程没有被打断并且可以通过这个workers加上锁,那么就将此线程打断。
从这个判断我们可以看出,线程池认为如果这个Worker没有通过w.lock()加锁,那么它就是空闲的,
与我们之前看到的”执行任务之前都会使用w.lock加锁“相对应。
总结:Worker在执行真正task任务之前,需要通过w.lock()加锁的原因就是防止线程执行任务过程中,被线程池给中断。但是线程处于阻塞状态时,即空闲状态,可以被打断。