ExecutorService简介
ExecutorService提供了一个异步任务管理的容器,可以关闭并可以通过Future来跟踪异步任务状态
ExecutorService定义了两个容器关闭的方法
/**
* Initiates an orderly shutdown in which previously submitted
* tasks are executed, but no new tasks will be accepted.
* Invocation has no additional effect if already shut down.
*
* 已经加入线程池的任务会被执行,但是不会再接收新的任务
*
*/
void shutdown();
/**
* Attempts to stop all actively executing tasks, halts the
* processing of waiting tasks, and returns a list of the tasks
* that were awaiting execution.
*
* 尝试中断(执行中)或者停止(待执行)所有的任务
*/
List<Runnable> shutdownNow();
线程池的背景知识
工作线程:工作线程从任务队列不停的取任务并执行,空闲线程会阻塞在任务队列上。
任务队列:任务提交,如果核心线程数不够,则创建worker并把此任务作为firstTask交付给worker(并不提交到任务队列,减少线程切换等性能损耗)。如果核心线程数满了,则把任务递交到任务队列,唤醒阻塞在任务队列上的空闲线程来拉取任务,同时看情况是否新增非核心工作线程
线程池的五种状态与核心容器关系
线程池状态 | 任务队列 | 工作线程 |
RUNNING | 可以提交任务 | 可以正常拉取任务并执行 |
SHUTDOWN | 不能再提交任务 | 正常拉取任务并执行 |
STOP | 不能再提交任务,并且任务队列被清空 | 空闲线程被唤醒退出 执行中的线程被中断(如果没有阻塞,interrupt不生效) |
TIDYING | 任务队列为空 | 执行中的task全部完成或者中止 |
TERMIANTED | 任务队列为空 | terminate hook被执行 |
线程池shutdown方法
public void shutdown() {
//主锁,用于保护线程池的状态,将锁引用copy到本地线程栈
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
//状态置为SHUTDOWN,此时在任务队列中的task与被工作线程持有的task任然会被正常执行
advanceRunState(SHUTDOWN);
//中断所有空闲的线程,工作线程状态机参见附录
interruptIdleWorkers();
//hook
onShutdown();
} finally {
mainLock.unlock();
}
tryTerminate();
}
一个典型spring线程池的停止的示例,意图在bean被销毁后,等待线程池中的任务被执行完毕,再退出这个bean的生命周期。
我的期望是,在spring优雅的停机时,能够等待线程池中所有的任务被执行完成,再停止服务。很明显,这种做法是有问题的,shutdown只是给线程池一个信号,不再接收任务的提,但并不保证任务都会被正确的执行!!!
一种可行的方式
void shutdownAndAwaitTermination(ExecutorService pool) {
pool.shutdown();
try {
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
}
} catch (InterruptedException ie) {
pool.shutdownNow();
Thread.currentThread().interrupt();
}
}}
事实上,我们先给线程池一个信号,让它不再接受任务提交请求,再者,注册一个hook,在线程池任务为空,并且工作线程全部关闭后通知我们(awaitTermination)。
awaitTermination方法分析:
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (;;) {
//如果线程池是TERMINATED状态,则直接退出,否则阻塞等待唤醒
if (runStateAtLeast(ctl.get(), TERMINATED))
return true;
if (nanos <= 0)
return false;
nanos = termination.awaitNanos(nanos);
}
} finally {
mainLock.unlock();
}
}
dubbo的线程池停止方法,具体方法在
com.alibaba.dubbo.common.utils.ExecutorUtil#gracefulShutdown
public static void gracefulShutdown(Executor executor, int timeout) {
if (!(executor instanceof ExecutorService) || isTerminated(executor)) {
return;
}
final ExecutorService es = (ExecutorService) executor;
try {
// Disable new tasks from being submitted
es.shutdown();
} catch (SecurityException ex2) {
return;
} catch (NullPointerException ex2) {
return;
}
try {
// Wait a while for existing tasks to terminate
if (!es.awaitTermination(timeout, TimeUnit.MILLISECONDS)) {
es.shutdownNow();
}
} catch (InterruptedException ex) {
es.shutdownNow();
Thread.currentThread().interrupt();
}
//在超时时间内如果还是没有能够停止,新开一个线程池来执行没有来得及执行的任务
if (!isTerminated(es)) {
newThreadToCloseExecutor(es);
}
}
总结: 在读dubbo的源码时,发现dubbo线程停止的方法比我预想的要繁杂,于是研究dubbo为什么是这样关闭线程,好奇心驱使我重新读了一遍线程池源码,才一窥其中的原理。
shutdownNow和shuwdown调用完,线程池并不是立马就关闭了,要想等待线程池关闭,还需调用awaitTermination方法来判断。
同时,我们在设计一个容器时,不仅仅需要能够完成这个容器的功能,同时也需要给外部提供容器的生命周期窥探已经管理的功能,这样的设计才是有生命力的。
附:worker源码
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
.....
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
// Lock methods
//
// The value 0 represents the unlocked state.
// The value 1 represents the locked state.
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
woker线程为什么要继承AbstractQueuedSynchronizer??
在容器shutdown时,我们发现容器中的任务队列很好控制,只要加主锁,更新容器状态,就能实现任务不能提交。
但是对于woker,如何感知工作线程的状态已经启动或者关闭,这里实现了一个简单的AQS,事实上是为了实现工作线程的锁(用于对worker状态的同步控制)以及巧妙的更新了工作线程状态
worker状态:
-1:初始态,worker被创建,但还未开始获取任务
0:工作线程启动,但是暂时没有任务处理
1:worker获取到了一个任务,并在执行