我们知道线程池中有三个要素,线程/任务/等待队列,任务需要被执行,线程执行任务,但是线程资源是有限的,所以需要等待队列存放任务。
线程池的执行流程
- 首先会判断线程池的状态是否为Running,只有Running状态下才允许提交任务。
- 会判断工作线程数是否小于核心线程数,如果小于,会新建一个线程去执行任务。
- 如果工作线程数大于核心线程数但小于最大的线程数,且此时等待队列还没有满时,任务会进入等待队列中去。
- 如果工作线程数大于核心线程数但小于最大的线程数,且等待队列已经满了,这个时候会新建一个线程去执行任务。
- 如果工作线程数已经达到最大线程数,且这个时候等待队列也满了,这个时候会根据拒绝策略处理任务,默认的拒绝策略AbortPolicy丢弃任务,报异常。
线程执行任务 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;
/** 构造方法 */
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
}
从上知,线程池通过Worker类将任务和线程绑定,Worker类实现了Runnable接口,继承了AQS,我们接着从源码看线程池是如何通过Worker类来执行任务的。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
// 获取Worker对象中的任务
Runnable task = w.firstTask;
// 将Worker对象中的任务置为null
w.firstTask = null;
// 释放锁
w.unlock();
boolean completedAbruptly = true;
try {
// 从getTask()中不断获取任务,直到空跳出循环,进入processWorkerExit()销毁工作线程
while (task != null || (task = getTask()) != null) {
w.lock();
......
try {
......
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
}
......
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
线程池不断通过while()循环从getTask()获取task任务,通过task.run()执行,这里其实就是线程的执行。那线程池是如何保护任务的正常执行的呢?是的,AQS独占锁,w.lock(),每次获取任务,就上一把锁,执行完毕后释放锁,通过锁机制来保护任务的执行。
线程获取任务 解析getTask()
private Runnable getTask() {
boolean timedOut = false;
for (;;) {
// 当前的工作线程数
int wc = workerCountOf(c);
// 允许核心线程超时 || 工作线程数大于核心线程数
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 走workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)有两种情况
// 1、要么允许核心线程失败
// 2、要么当前工作线程数大于核心线程数
// 走workQueue.take()
// 当前的工作线程数等于核心线程数,且不允许核心线程数超时,通过workQueue.take()进行阻塞。
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
// 如果任务不为空,返回任务
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
workQueue.pool(keepAliveTime, TimeUnit) 从队列中获取元素,当阻塞时间keepAliveTime到了还没获取元素会返回null
workQueue.take()一直阻塞直到获取元素或线程中断
线程池不断的将队列中的任务pool()出来执行,当在超时时间内仍然无法获取任务即任务被执行完了,会返回一个空任务,返回runWorker方法跳出循环,回收空闲线程。 当回收到corePoolSize阈值时,这个时候会通过allowCoreThreadTimeOut来判断是否需要通过take()方法来保证剩下线程的存活。
线程的回收 processWorkerExit()
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// 如果是异常完成,通过CAS workCount - 1
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
// 加锁,因为works所属的HashSet不是线程安全的,所以需要加锁控制
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
// 从works从移除空闲的work线程
workers.remove(w);
} finally {
mainLock.unlock();
}
// 尝试去终止线程池
tryTerminate();
int c = ctl.get();
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
// 如果允许核心线程销毁,min = 0,否则min=核心线程数
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
// 如果队列不为空,还有任务待处理,min = 1
min = 1;
if (workerCountOf(c) >= min)
// 不需要addWorker直接返回
return; // replacement not needed
}
addWorker(null, false);
}
}
线程池维护了一个类型为Worker的集合(HashSet),通过将Worker从集合中移除,达到回收线程的目的。如果线程数被回收为0了,假设这个时候我的队列中仍然存在一个任务咋办?这时线程池会通过addWorker()方法去新建一个线程处理任务。所以线程池中线程数为空的条件不是所有的线程都空闲,而是等待队列里是否存在任务。
如果停止一个线程池 shutdown()\shutdownNow()
线程池提供了两个命令来让我们去停止线程池
- shutdown():会等待目前正在执行的任务执行好后,关闭线程池,是一种柔性中断。
- shutdownNow(): 会中断正常执行的任务,关闭线程池,将任务通过drainQueue()转移到一个List,便于后续做一些补救措施,是一种刚性中断。
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 检查关闭权限
checkShutdownAccess();
// 将线程池的状态变更为SHUTDOWN(任务可以继续执行,但是不接受新的任务)
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
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();
}
}
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 检查关闭权限
checkShutdownAccess();
// 将线程池的状态变更为STOP(中断任务,不再接受任务)
advanceRunState(STOP);
// 中断所有工作线程
interruptWorkers();
// 将阻塞队列中的任务,移动到task集合中
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
线程中断的原理 详解interrupt()和interrupted()的差别
- interrupt() thread.interrupt()是直接中断线程吗?答案是 【不是】,只是通过interrupt0()给调用这个方法的线程设置中断状态,不直接中断
- interrupted() 检查当前线程的中断标志位,同时清除中断标志,改为false, 这里需要明确的是当前线程,而不是调用线程。
public void interrupt() {
if (this != Thread.currentThread())
// 如果不是thread本身需要检查其权限
checkAccess();
// 对象锁控制并发
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
// 只是设置中断标志
interrupt0();
b.interrupt(this);
return;
}
}
interrupt0();
}
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
线程池中的锁的作用 mainLock
- addWorker() 保护工作线程集合避免并发增加工作线程、保护度量统计数据变更
- interruptIdleWorkers() 保护工作线程中断的串行化,避免"中断风暴"
- tryTerminate() 保证状态TIDYING -> TERMINATED,钩子方法terminated()回调和条件变量唤醒
参考资料
最后
自我总结,如果有错误或不对之处,请随时留言指出,如果有职业上的问题交流可以加我的VX:DOUFOR,很高兴能与你进行技术、生活且不限于方向上的交流。