1. shutdown() 和shutdownNow()
shutdown()
和 shutdownNow()
是 java.util.concurrent.ExecutorService
接口中定义的两个方法,用于控制线程池的关闭行为。这两个方法的主要区别在于它们处理线程池中当前正在执行的任务和队列中等待的任务的方式不同。
shutdown()
shutdown()
方法尝试平滑地关闭线程池,也就是说,它不再接受新的任务,但允许已经提交的任务完成执行。具体来说:
-
停止接受新任务:
shutdown()
会停止线程池接受新的任务提交。
-
允许当前任务完成:
- 已经提交给线程池的任务将继续执行直到完成。
- 任务队列中的任务也将被处理。
-
返回值:
shutdown()
方法没有返回值,它返回void
。
-
示例调用:
executor.shutdown();
shutdownNow()
shutdownNow()
方法尝试立即关闭线程池,并尝试取消正在执行的任务。具体来说:
-
停止接受新任务:
- 与
shutdown()
相同,shutdownNow()
也会停止线程池接受新的任务提交。
- 与
-
尝试取消当前任务:
shutdownNow()
尝试取消所有正在执行的任务,并清空任务队列。- 这个方法并不会等待当前正在执行的任务完成,而是尝试中断它们。
-
返回值:
shutdownNow()
返回一个List
,其中包含了尚未开始执行的任务以及被成功取消的任务。- 这个列表不包括那些在
shutdownNow()
被调用之前已经开始执行且未能被取消的任务。
-
示例调用:
List<Runnable> cancelledTasks = executor.shutdownNow();
总结
-
shutdown():
- 停止接受新任务,允许已提交的任务完成。
- 适用于希望优雅地关闭线程池,并等待所有任务执行完成的情况。
-
shutdownNow():
- 停止接受新任务,尝试取消正在执行的任务,并清空任务队列。
- 适用于需要尽快关闭线程池,即使这意味着中断正在执行的任务的情况。
选择哪个方法取决于您的应用程序的具体需求。如果需要尽可能快地关闭线程池而不关心正在执行的任务,可以选择 shutdownNow()
。如果希望等待所有任务执行完毕再关闭线程池,可以选择 shutdown()
。
示例代码
下面是使用 shutdown() 和 shutdownNow() 的示例代码:
import java.util.concurrent.*;
public class ShutdownExample {
public static void main(String[] args) throws InterruptedException {
// 创建一个线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
// 提交多个任务
for (int i = 0; i < 10; i++) {
executor.submit(new Task(i));
}
// 使用 shutdown() 平滑关闭线程池
executor.shutdown();
// 等待所有任务完成
while (!executor.isTerminated()) {
// 空循环体
}
System.out.println("All tasks completed with shutdown.");
// 重新创建线程池
executor = Executors.newFixedThreadPool(5);
// 提交多个任务
for (int i = 0; i < 10; i++) {
executor.submit(new Task(i));
}
// 使用 shutdownNow() 尝试立即关闭线程池
List<Runnable> cancelledTasks = executor.shutdownNow();
System.out.println("Cancelled tasks: " + cancelledTasks.size());
System.out.println("All tasks completed with shutdownNow.");
}
static class Task implements Runnable {
private int taskId;
public Task(int id) {
this.taskId = id;
}
@Override
public void run() {
System.out.println("Task " + taskId + " started by " + Thread.currentThread().getName());
try {
Thread.sleep(5000); // 模拟长时间运行的任务
} catch (InterruptedException e) {
System.out.println("Task " + taskId + " interrupted.");
Thread.currentThread().interrupt();
}
System.out.println("Task " + taskId + " finished.");
}
}
}
2. 如何正确的关闭线程池?
正确关闭线程池是非常重要的,这有助于确保所有任务都能够优雅地完成,并且线程池资源能够得到适当的释放。以下是正确关闭线程池的一般步骤:
步骤 1: 停止接受新任务
使用 shutdown()
方法来停止线程池接受新的任务提交。这一步不会立即中断正在执行的任务,而是确保线程池不再接受新的任务。
executor.shutdown();
步骤 2: 等待所有任务完成
调用 isTerminated()
方法来检查线程池是否已经完成了所有任务。如果 isTerminated()
返回 false
,说明还有任务在执行或等待执行。你可以使用循环来等待所有任务完成。
while (!executor.isTerminated()) {
// 等待所有任务完成
}
如果你知道线程池将在一段时间后关闭,也可以使用 awaitTermination()
方法来等待所有任务完成,或者在等待一定时间后返回。
try {
// 等待所有任务完成
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
System.out.println("Timeout reached, some tasks may not have completed.");
}
} catch (InterruptedException e) {
System.err.println("Shutdown was interrupted.");
Thread.currentThread().interrupt();
}
步骤 3: 强制关闭(如果必要)
如果需要立即关闭线程池,可以使用 shutdownNow()
方法。这将尝试取消正在执行的任务,并返回一个包含未开始执行的任务的列表。
List<Runnable> remainingTasks = executor.shutdownNow();
示例代码
下面是一个完整的示例,展示如何正确关闭线程池:
import java.util.List;
import java.util.concurrent.*;
public class ProperShutdownExample {
public static void main(String[] args) throws InterruptedException {
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
// 提交任务
for (int i = 0; i < 10; i++) {
executor.submit(new Task(i));
}
// 停止接受新任务
executor.shutdown();
// 等待所有任务完成
while (!executor.isTerminated()) {
// 空循环体
}
System.out.println("All tasks completed.");
// 如果需要立即关闭线程池
// List<Runnable> remainingTasks = executor.shutdownNow();
// System.out.println("Remaining tasks: " + remainingTasks.size());
}
static class Task implements Runnable {
private int taskId;
public Task(int id) {
this.taskId = id;
}
@Override
public void run() {
System.out.println("Task " + taskId + " started by " + Thread.currentThread().getName());
try {
Thread.sleep(5000); // 模拟长时间运行的任务
} catch (InterruptedException e) {
System.out.println("Task " + taskId + " interrupted.");
Thread.currentThread().interrupt();
}
System.out.println("Task " + taskId + " finished.");
}
}
}
在这个示例中,我们首先使用 shutdown()
方法来停止线程池接受新的任务提交。然后,我们使用 isTerminated()
方法在一个循环中等待所有任务完成。如果需要立即关闭线程池,可以使用 shutdownNow()
方法。
注意事项
-
异常处理:
- 如果在等待过程中发生中断,应恢复中断状态,以便后续代码可以检测到中断。
-
资源清理:
- 确保在关闭线程池前完成任何必要的资源清理操作。
-
超时:
- 使用
awaitTermination()
时,考虑设置合理的超时时间,以防无限期等待。
正确关闭线程池不仅可以确保所有任务都按预期执行,还可以避免资源泄露和异常状态。
- 使用
关闭线程池的关键是 shutdown + awaitTermination或者 shutdownNow + awaitTermination
官网提供的优雅关停思路
/**
* 参考官网使用,最后的终结,优雅关停,but有点费事
* @param threadPool
*/
public static void finalOK_shutdownAndAwaitTermination(ExecutorService threadPool)
{
if (threadPool != null && !threadPool.isShutdown())
{
threadPool.shutdown();
try
{
if (!threadPool.awaitTermination(120, TimeUnit.SECONDS))
{
threadPool.shutdownNow();
if (!threadPool.awaitTermination(120, TimeUnit.SECONDS))
{
System.out.println("Pool did not terminate");
}
}
} catch (InterruptedException ie) {
threadPool.shutdownNow();
Thread.currentThread().interrupt();
}
}
}