ThreadPoolExecutor原理解析-关闭线程池

文章详细分析了Java中ThreadPoolExecutor的关闭方法,包括shutdown()和shutdownNow()。shutdown()方法会将线程池状态设为SHUTDOWN,中断空闲工作线程,等待所有任务执行完毕后关闭;而shutdownNow()则会立即停止所有工作线程,尝试中断正在执行的任务,并返回未执行的任务列表。tryTerminate()方法在合适的时机尝试终止线程池。

大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈


前言

关闭ThreadPoolExecutor的方法有shutdown()shutdownNow()方法,本篇文章将对ThreadPoolExecutor的关闭进行分析。

正文

一. shutdown()

首先分析shutdown()方法,其实现如下。

public void shutdown() {
	final ReentrantLock mainLock = this.mainLock;
	mainLock.lock();
	try {
		checkShutdownAccess();
		// 循环通过CAS方式将线程池状态置为SHUTDOWN
		advanceRunState(SHUTDOWN);
		// 中断空闲Worker
		interruptIdleWorkers();
		onShutdown();
	} finally {
		mainLock.unlock();
	}
	// 尝试终止线程池
	tryTerminate();
}

shutdown()方法中首先会将线程池状态置为SHUTDOWN,然后调用interruptIdleWorkers()方法中断空闲Worker,最后调用tryTerminate()方法来尝试终止线程池。那么这里要解释一下什么是空闲Worker,先看一下interruptIdleWorkers()的实现。

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的锁,所以shutdown()方法只会中断空闲Worker
			if (!t.isInterrupted() && w.tryLock()) {
				try {
					t.interrupt();
				} catch (SecurityException ignore) {
				} finally {
					w.unlock();
				}
			}
			if (onlyOne)
				break;
		}
	} finally {
		mainLock.unlock();
	}
}

调用interruptIdleWorkers()方法中断Worker前首先需要尝试获取Worker的锁,已知Worker除了实现Runnable接口外,还继承于AbstractQueuedSynchronizer,因此Worker本身是一把锁,然后在runWorker()Worker执行任务前都会先获取Worker的锁,这里看一下Workerlock()方法的实现。

public void lock() { 
	acquire(1);
}

protected boolean tryAcquire(int unused) {
	// 以CAS方式将state从0设置为1
	if (compareAndSetState(0, 1)) {
		setExclusiveOwnerThread(Thread.currentThread());
		return true;
	}
	return false;
}

可以发现,Workerlock()中调用了acquire()方法,该方法由AbstractQueuedSynchronizer抽象类提供,在acquire()中会调用其子类实现的tryAcquire()方法,tryAcquire()方法会以CAS方式将state从0设置为1,因此这样的设计让Worker是一把不可重入锁。回到interruptIdleWorkers()方法,前面提到该方法中断Worker前会尝试获取Worker的锁,能够获取到锁才会中断Worker,而因为Worker是不可重入锁,所以正在执行任务的Worker是无法获取到锁的,只有那些没有执行任务的Worker的锁才能够被获取,因此所谓的中断空闲Worker,实际就是中断没有执行任务的Worker,那些执行任务的Workershutdown()方法被调用时不会被中断,这些Worker执行完任务后会继续从任务阻塞队列中获取任务来执行,直到任务阻塞队列为空,此时没有被中断过的Worker也会被删除掉,等到线程池中没有Worker以及任务阻塞队列没有任务后,线程池才会被终止掉。

对于shutdown()方法,一句话总结就是:将线程池状态置为SHUTDOWN并拒绝接受新任务,等到线程池Worker数量为0,任务阻塞队列为空时,关闭线程池。

二. shutdownNow()

现在再来分析shutdownNow()方法。

public List<Runnable> shutdownNow() {
	List<Runnable> tasks;
	final ReentrantLock mainLock = this.mainLock;
	mainLock.lock();
	try {
		checkShutdownAccess();
		// 循环通过CAS方式将线程池状态置为STOP
		advanceRunState(STOP);
		// 中断所有Worker
		interruptWorkers();
		// 将任务阻塞队列中的任务获取出来并返回
		tasks = drainQueue();
	} finally {
		mainLock.unlock();
	}
	// 尝试终止线程池
	tryTerminate();
	return tasks;
}

private void interruptWorkers() {
	final ReentrantLock mainLock = this.mainLock;
	mainLock.lock();
	try {
		// 中断线程池中所有Worker
		for (Worker w : workers)
			w.interruptIfStarted();
	} finally {
		mainLock.unlock();
	}
}

shutdownNow()方法中首先会将线程池状态置为STOP,然后调用interruptWorkers()方法中断线程池中的所有Worker,接着调用tryTerminate()方法来尝试终止线程池,最后shutdownNow()方法会将任务阻塞队列中还未被执行的任务返回。shutdownNow()方法调用之后,线程池中的所有Worker都会被中断,包括正在执行任务的Worker,等到所有Worker都被删除之后,线程池即被终止,也就是说,shutdownNow()不会保证当前时刻正在执行的任务会被安全的执行完,并且会放弃执行任务阻塞队列中的所有任务。

三. tryTerminate()

关于线程池的关闭,还有一个重要的方法,那就是前面多次提到的tryTerminate()方法,该方法能确保线程池可以被正确的关闭,其实现如下所示。

final void tryTerminate() {
	for (;;) {
		int c = ctl.get();
		// 如果线程池状态为RUNNING,则没有资格终止线程池
		// 如果线程池状态大于等于TIDYING,则没有资格终止线程池
		// 如果线程池状态为SHUTDOWN但任务阻塞队列不为空,则没有资格终止线程池
		if (isRunning(c) ||
			runStateAtLeast(c, TIDYING) ||
			(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
			return;
		// 线程池状态为SHUTDOWN且任务阻塞队列为空会执行到这里
		// 线程池状态为STOP会执行到这里
		// Worker数量不为0,表明当前还有正在执行任务的Worker或者空闲的Worker,此时中断一个空闲的Worker
		// 在这里被中断的空闲Worker会在getTask()方法中返回null,从而执行processWorkerExit(),最终该Worker会被删除
		// processWorkerExit()方法中又会调用tryTerminate(),因此将shutdown信号在空闲Worker之间进行了传播
		if (workerCountOf(c) != 0) {
			interruptIdleWorkers(ONLY_ONE);
			return;
		}

		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();
		}
	}
}

tryTerminate()方法的官方注释中给出了两种线程池会被终止的情况:

  • 线程池的状态为SHUTDOWNWorker数量为0,任务阻塞队列为空;
  • 线程池的状态为STOPWorker数量为0。

官方注释中还说明在所有可能导致线程池终止的操作中都应该调用tryTerminate()方法来尝试终止线程池,因此线程池中Worker被删除时任务阻塞队列中任务被删除时会调用tryTerminate(),以达到在线程池符合终止条件时及时终止线程池。

总结

关闭ThreadPoolExecutor有两种方式,总结如下。

  1. shutdown()。调用shutdown()方法会首先将线程池状态置为SHUTDOWN并拒绝接受新任务,然后中断空闲Worker,等到线程池中Worker数量为0,任务阻塞队列为空时,线程池被真正关闭;
  2. shutdownNow()。调用shutdownNow()方法会首先将线程池状态置为STOP,然后中断所有Worker(包括正在执行任务的Worker),并将任务阻塞队列中还未被执行的任务返回,当线程池Worker数量为0时,线程池被真正关闭。

还有一点需要说明,Worker除了实现Runnable接口外,还继承于AbstractQueuedSynchronizer,因此Worker本身是一把锁,Worker执行任务前都会先获取Worker的锁,所以正在执行任务的Worker的锁是无法被获取的,换言之,只有没有执行任务的Worker的锁才能被获取,这些Worker就称为空闲Worker


大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

半夏之沫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值