java线程池终止_Java线程池详解3--线程池终止

线程池终止主要依靠以下2个命令:

shutdown()

shutdownNow()

首先看一下shutdown方法:

shutdown

public void shutdown() {

final ReentrantLock mainLock = this.mainLock;

// 获取独占锁

mainLock.lock();

try {

// 检查各worker是否可操作

checkShutdownAccess();

// 将线程池状态更新为SHUTDOWN

advanceRunState(SHUTDOWN);

// 中断空闲线程

interruptIdleWorkers();

// 响应shutdown操作,由ThreadPoolExecutor的继承类来具体实现

onShutdown();

} finally {

mainLock.unlock();

}

// 执行tryTerminate()操作

tryTerminate();

}

advanceRunState

private void advanceRunState(int targetState) {

// 阻塞执行

for (;;) {

int c = ctl.get();

// 1. 若当前线程池状态>=targetState,直接break

// 2. 将线程池的状态更新为targetState,并在ctl中保留当前的工作线程数,若成功,则直接break

if (runStateAtLeast(c, targetState) ||

ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))

break;

}

}

执行完advanceRunState方法后,当前线程池状态肯定已>=targetState。

interruptIdleWorkers

该方法用于中断线程池中的空闲线程。

在前面系列文章中我们讲过,worker在执行任务前会先获取锁,执行完任务则释放锁,所以处于锁定状态的worker(state为1)为工作线程,而处于无锁状态的worker(state为0)为空闲线程。

private void interruptIdleWorkers() {

interruptIdleWorkers(false);

}

private void interruptIdleWorkers(boolean onlyOne) {

final ReentrantLock mainLock = this.mainLock;

// 获取线程池独占锁

mainLock.lock();

try {

for (Worker w : workers) {

Thread t = w.thread;

// 若当前worker持有的线程未被中断过,且获取worker锁成功,则执行线程中断操作

// 若获取worker锁不成功,证明该线程为工作线程,不执行线程中断操作

if (!t.isInterrupted() && w.tryLock()) {

try {

t.interrupt();

} catch (SecurityException ignore) {

} finally {

w.unlock();

}

}

// 仅执行1次即退出

if (onlyOne)

break;

}

} finally {

mainLock.unlock();

}

}

待线程池状态更新为shutdown,且所有空闲线程被中断,则执行onShutdown方法来响应shutdown操作。

上述过程执行完之后,执行tryTerminate()操作来终止线程池。

tryTerminate

final void tryTerminate() {

for (;;) {

int c = ctl.get();

// 出现下述3种情况,直接return:

// 1. 当前线程池处于running状态

// 2. 当前线程池处于tidying或terminated状态

// 3. 当前线程池处于shutdown状态,且workQueue不为空,此时只是拒绝提交新任务,但workQueue中的任务还需要继续执行完

if (isRunning(c) ||

runStateAtLeast(c, TIDYING) ||

(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))

return;

// 代码走到这,说明为以下2种情况:

// 1. 当前线程池处于stop状态

// 2. 当前线程池处于shutdown状态,且workQueue为空

if (workerCountOf(c) != 0) {

// 中断唤醒1个正在等任务的空闲worker

interruptIdleWorkers(ONLY_ONE);

return;

}

// 此时,线程池状态为shutdown,workQueue为空,且正在运行的worker也没有了,开始terminated()

final ReentrantLock mainLock = this.mainLock;

mainLock.lock();

try {

// 将线程池状态更新为tidying

if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {

try {

// 此方法由线程池子类来定义

terminated();

} finally {

// 将线程池状态更新为terminated

ctl.set(ctlOf(TERMINATED, 0));

termination.signalAll();

}

return;

}

} finally {

mainLock.unlock();

}

}

}

假设如下情景:

当发出shutdown命令时,线程池还有3个核心工作线程,由于interruptIdleWorkers()仅会中断空闲线程,所以这3个核心工作线程不会被中断。

上述3个核心工作线程执行完自身携带和阻塞队列中的任务后,会变为空闲核心线程。由于此时线程状态已变为shutdown,所以会从runWorker()的while循环中(线程池状态为shutdown时,getTask会返回null)跳出,执行finally代码块中的processWorkerExit方法。

而processWorkerExit又会执行tryTerminate方法。所以tryTerminate会不断循环传递调用,tryTerminate方法的interruptIdleWorkers(ONLY_ONE)看起来仅是中断一个空闲线程,但其实这个中断信号会引发一个"循环中断风暴",直到线程池中所有worker被中断。

不得不说,Doug Lea是真大神呀!!!这种设计真是巧妙到极点!!!

梳理完shutdown整个代码后可以发现,线程池状态的变化过程如下:

running-->shutdown-->tidying-->terminated。

执行完shutdown,线程池状态首先会更新为shutdown,然后中断所有空闲线程,当剩余工作线程执行完持有的任务,且将阻塞队列中的任务也执行完毕,变为空闲线程时,执行tryTerminate()操作将线程池状态更新为tidying,待线程池完成terminated()操作后,线程池状态最终变为terminated。

趁热打铁,接着看一下shutdownNow方法:

shutdownNow

public List shutdownNow() {

List tasks;

final ReentrantLock mainLock = this.mainLock;

// 获取线程池独占锁

mainLock.lock();

try {

// 检查各worker是否可操作

checkShutdownAccess();

// 将线程池状态更新为STOP

advanceRunState(STOP);

// 尝试中断所有已启动的worker

interruptWorkers();

// 将阻塞队列中的任务清空

tasks = drainQueue();

} finally {

mainLock.unlock();

}

// 执行tryTerminate()操作

tryTerminate();

// 返回任务集合

return tasks;

}

interruptWorkers

private void interruptWorkers() {

final ReentrantLock mainLock = this.mainLock;

mainLock.lock();

try {

for (Worker w : workers)

// 中断已启动的线程

w.interruptIfStarted();

} finally {

mainLock.unlock();

}

}

void interruptIfStarted() {

Thread t;

if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {

try {

t.interrupt();

} catch (SecurityException ignore) {

}

}

}

可以发现,中断worker时,会首先执行判断getState() >= 0,前面我们已经说过了,worker除了工作线程(state为1),就是空闲线程(state为0),那getState() >= 0恒成立呀,为啥多此一举呢?

其实worker刚初始化未启动前,其状态为-1,执行到runWorker时,会执行w.unlock()操作将状态修改为0,此时线程才可被中断。

方法如其名,interruptIfStarted仅能中断已启动的worker。

Worker(Runnable firstTask) {

setState(-1); // inhibit interrupts until runWorker

this.firstTask = firstTask;

this.thread = getThreadFactory().newThread(this);

}

drainQueue

private List drainQueue() {

BlockingQueue q = workQueue;

ArrayList taskList = new ArrayList();

// 首先通过阻塞队列的drainTo方法将队列中的Runnable转移到taskList中

q.drainTo(taskList);

// 如果阻塞队列是DelayQueue或者阻塞队列执行poll或drainTo操作失败,则需要通过遍历的方法完成Runnable转移操作

if (!q.isEmpty()) {

for (Runnable r : q.toArray(new Runnable[0])) {

if (q.remove(r))

taskList.add(r);

}

}

// 返回阻塞队列中的任务集合

return taskList;

}

综上,drainQueue主要完成2种操作:

清空阻塞队列中的元素;

将阻塞队列中的元素保存到List中返回。

梳理完shutdownNow整个代码后可以发现,线程池状态的变化过程如下:

running-->stop-->tidying-->terminated。

执行完shutdownNow,线程池状态首先会更新为stop,接着中断所有已启动worker,然后执行tryTerminate()操作将线程池状态更新为tidying,待线程池完成terminated()操作后,线程池状态最终变为terminated。

可以发现,对线程池执行shutdown或shutdownNow后,线程池状态均需要一系列的流程才能将线程池状态更新为terminated。

那如何确认当前线程池已处于terminated状态呢?

线程池为此提供了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,则直接返回true

if (runStateAtLeast(ctl.get(), TERMINATED))

return true;

// 若nanos <= 0,则直接返回false

if (nanos <= 0)

return false;

// 阻塞等待nanos,并返回新的nanos

nanos = termination.awaitNanos(nanos);

}

} finally {

mainLock.unlock();

}

}

所以线程池终止的最佳实践是:

首先对线程池执行shutdown操作;

然后通过awaitTermination方法判断当前线程池状态是否为terminated。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值