对于我们使用的线程池 ThreadPoolExecutor 来说,停止线程池的方法有以下两个:
- shutdown() :优雅的关闭线程池,即不再接受新任务,但会等待已提交任务(包括正在执行的任务和在队列中等待的任务)执行完毕。等待所有任务都执行完毕后,线程池才会进入终止状态。
- shutdownNow() :尝试停止所有正在执行的任务,并返回等待执行的任务列表。<font style="color:rgba(0, 0, 0, 0.85);">正在执行的任务可能会被中断</font><font style="color:rgba(0, 0, 0, 0.85);">,适用于</font>需要立即停止线程池,但不关心正在执行的任务是否立即完成的情况下。
1.代码演示
下面通过代码案例,咱们来了解一下 shutdown() 和 shutdownNow() 方法的具体使用。
1.1 shutdown() 方法执行
我们将线程池核心和最大线程数都设置为 2,任务队列可以存储 10 个任务,一次性添加了 5 个任务,每个任务执行 2s 以上,添加完任务之后执行停止方法,并在 1s 之后尝试添加另一个新任务,如下代码所示:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExecutorShutdownTest {
public static void main(String[] args) {
// 创建线程
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2,
2,
1000,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(10),
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("执行拒绝策略");
}
});
// 添加任务
for (int i = 0; i < 5; i++) {
executor.submit(() -> {
String tName = Thread.currentThread().getName();
System.out.println(tName + ":开始执行任务!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(tName + ":结束执行任务!");
});
}
// 停止线程
executor.shutdown();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 添加新任务
executor.submit(() -> System.out.println("最后一个新任务"));
}
}
以上程序的执行结果如下:
从以上结果可以看出,执行 shutdown() 方法后,程序会等待线程池中的所有任务全部执行完在关闭,再次期间线程池会拒绝加入新任务,并调用线程池的拒绝策略。
1.2 shutdownNow()方法执行
如果将 shutdown() 方法换成 shutdownNow() 方法后,以上程序的执行结果如下:
也就是说,调用 shutdownNow() 之后,正在执行的任务会被立即停止,且任务队列中未执行的任务也会被清除,调用 shutdownNow() 方法后新加入的任务会被拒绝,并执行线程池的拒绝策略。
2.shutdown()执行流程
shutdown() 方法执行源码如下:
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
该源码执行流程如下:
- 加锁:在多线程环境下,关闭操作涉及到修改关键状态和执行一些可能影响多个线程的操作。使用锁可以确保这些操作的原子性和一致性,避免多个线程同时进行关闭操作导致数据不一致或出现意外情况
- 检查关闭权限:在关闭之前进行状态检查可以确保关闭操作是合法的,避免在不适当的时候进行关闭。推进状态可以让其他代码部分能够根据当前执行器的状态做出正确的反应。
- 将状态设置为 SHUTDOWN:阻止新任务提交但完成现有任务。
- 中断空闲线程。
- 调用 onShutdown 方法(钩子方法) :<font style="color:rgba(0, 0, 0, 0.85);">可能用于</font><font style="color:rgba(0, 0, 0, 0.85);">在关闭时执行一些特定的清理或自定义操作,比如释放资源</font><font style="color:rgba(0, 0, 0, 0.85);">等。</font>
- <font style="color:rgba(0, 0, 0, 0.85);">释放锁</font><font style="color:rgba(0, 0, 0, 0.85);">。</font>
- 尝试终止线程池:如果所有任务已完成的情况下,会真正的终止线程池。
shutdown() 方法的执行流程如下图所示:
课后思考
为什么需要关闭线程池?关闭线程池的场景有哪些?说说 shutdownNow() 的执行流程?
作为程序员,持续学习和充电非常重要,作为开发者,我们需要保持好奇心和学习热情,不断探索新的技术,只有这样,我们才能在这个快速发展的时代中立于不败之地。低代码也是一个值得我们深入探索的领域,让我们拭目以待,它将给前端世界带来怎样的变革,推荐一个低代码工具。
应用地址: https://www.jnpfsoft.com
开发语言:Java/.net
这是一个基于Flowable引擎(支持java、.NET),已支持MySQL、SqlServer、Oracle、PostgreSQL、DM(达梦)、 KingbaseES(人大金仓)6个数据库,支持私有化部署,前后端封装了上千个常用类,方便扩展,框架集成了表单、报表、图表、大屏等各种常用的 Demo方便直接使用。
至少包含表单建模、流程设计、报表可视化、代码生成器、系统管理、前端 UI 等组件,这种情况下我们避免了重复造轮子,已内置大量的成熟组件,选择合适的组件进行集成或二次开发复杂功能,即可自主开发一个属于自己的应用系统。