对于我们使用的线程池 ThreadPoolExecutor 来说,停止线程池的方法有以下两个:
- shutdown():优雅的关闭线程池,即不再接受新任务,但会等待已提交任务(包括正在执行的任务和在队列中等待的任务)执行完毕。等待所有任务都执行完毕后,线程池才会进入终止状态。
- shutdownNow():尝试停止所有正在执行的任务,并返回等待执行的任务列表。正在执行的任务可能会被中断,适用于需要立即停止线程池,但不关心正在执行的任务是否立即完成的情况下。
1.代码演示
下面通过代码案例,咱们来了解一下 shutdown() 和 shutdownNow() 方法的具体使用。
1.1 shutdown() 方法执行
我们将线程池核心和最大线程数都设置为 2,任务队列可以存储 10 个任务,一次性添加了 5 个任务,每个任务执行 2s 以上,添加完任务之后执行停止方法,并在 1s 之后尝试添加另一个新任务,如下代码所示:
代码语言:javascript复制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() 方法执行源码如下:
代码语言:javascript复制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 方法(钩子方法):可能用于在关闭时执行一些特定的清理或自定义操作,比如释放资源等。
- 释放锁。
- 尝试终止线程池:如果所有任务已完成的情况下,会真正的终止线程池。
shutdown() 方法的执行流程如下图所示:
课后思考
为什么需要关闭线程池?关闭线程池的场景有哪些?说说 shutdownNow() 的执行流程?
本文已收录到我的面试小站 www.javacn.site,其中包含的内容有:Redis、JVM、并发、并发、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、设计模式、消息队列等模块。