话不多说,直接上方案
第一步:通过ExecutorService的shutdown、awaitTermination以及shutdownNow实现线程的关闭和执行等待
第二步:通过Runtime.getRuntime().addShutdownHook(thread)声明虚拟机关机挂钩
1.ExecutorService线程操作
1.1.shutdownNow
是 Java 中 ExecutorService
接口的一个方法,用于尝试立即关闭线程池。当调用这个方法时,它会尝试执行以下操作:
- 停止所有正在执行的任务。
- 停止所有闲置(未被执行)的任务。
- 返回那些未得到执行的任务列表。
具体来说,shutdownNow()
会尝试停止所有正在执行的任务,并且不会启动队列中等待的任务。如果任务正在执行,那么这些任务可能会通过 Thread.interrupt()
方法来中断它们,但是任务是否能够响应中断完全取决于任务本身的实现。如果任务没有正确处理中断请求(即没有检查中断状态或者没有正确处理 InterruptedException
),那么即使调用了 shutdownNow()
,这些任务也可能会继续执行。
需要注意的是,shutdownNow()
不保证能立即停止所有正在执行的任务,因为这依赖于任务的响应情况和实现。此外,它返回的列表仅代表尚未执行的任务,并不包括可能已经启动但尚未完成的任务。
示例代码:
ExecutorService executor = Executors.newFixedThreadPool(10);
// 提交任务到线程池
// ...
// 尝试立即关闭线程池
List<Runnable> notExecutedTasks = executor.shutdownNow();
// 处理未执行的任务
for (Runnable task : notExecutedTasks) {
// 可以选择重新提交任务或其他操作
}
使用 shutdownNow()
后,线程池将不再接受新的任务。如果需要等待正在执行的任务完成,通常会在调用 shutdownNow()
之后调用 awaitTermination()
方法来等待线程池中的所有任务完成执行。
1.2.awaitTermination
Java中 ExecutorService
接口的一个方法,用于请求关闭线程池,并等待所有提交的任务完成执行,或者直到发生超时或当前线程中断。
这个方法通常用于确保程序在终止前,线程池中的任务都已经完成执行。
awaitTermination
方法的如下:
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
参数说明:
timeout
:最长等待时间。unit
:时间单位,是一个枚举类型TimeUnit
,可以是秒、毫秒等。
返回值:
- 返回一个布尔值,如果线程池在指定的超时时间内关闭,则返回
true
;如果超时时间到了但线程池还没有关闭,则返回false
。
示例代码:
ExecutorService executorService = Executors.newFixedThreadPool(10);
// 提交任务到线程池
executorService.submit(() -> {
// 任务代码
});
// 请求关闭线程池,不再接受新任务
executorService.shutdown();
try {
// 等待线程池终止,等待最长60秒
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
// 超时后尝试强制关闭
executorService.shutdownNow();
}
} catch (InterruptedException e) {
// 当前线程被中断操作
executorService.shutdownNow();
// 重新中断当前线程
Thread.currentThread().interrupt();
}
在上述代码中,首先通过 shutdown
方法发起关闭线程池的请求,然后通过 awaitTermination
方法等待线程池中的任务执行完毕或者等待60秒。如果60秒内线程池关闭,则继续执行后续操作;如果60秒后线程池仍然没有关闭,则通过 shutdownNow
方法尝试立即关闭线程池。如果等待过程中线程被中断,则会捕获 InterruptedException
异常,并通过 shutdownNow
方法立即关闭线程池。
2.jvm的关机挂钩
2.1.addShutdownHook
是Java提供的用于关闭钩子(Shutdown Hook)的方法。当JVM开始关闭过程时(通常是因为程序正常退出或因为接收到了系统的关闭信号),这些添加的钩子会被启动并执行。
以下是这个方法的一些关键点:
- 关闭钩子是一种初始化但未启动的线程,当JVM开始关闭过程时,系统会自动启动这些线程。
- 你可以添加多个关闭钩子。在JVM关闭时,它们将并发执行,所以关闭钩子之间不应存在依赖或同步要求。
- 一旦虚拟机开始执行关闭钩子,它将等待所有的钩子执行完毕才会进一步关闭。因此,关闭钩子的执行时间不应过长,以免延迟程序的关闭过程。
- 关闭钩子可以用于清理资源,例如关闭文件句柄、网络连接或释放其他系统资源。
- 在某些情况下,JVM可能不会执行关闭钩子,例如如果JVM是由于用户中断(如按下Ctrl+C)或系统级别的错误导致急速关闭。
示例代码:
// 创建一个新的线程作为Shutdown Hook
Thread shutdownHook = new Thread() {
public void run() {
System.out.println("Shutdown hook is running !");
// 这里可以放置清理资源的代码
}
};
// 添加Shutdown Hook
Runtime.getRuntime().addShutdownHook(shutdownHook);
在上述示例中,我们创建了一个匿名内部类的线程,重写了其 run
方法以打印一条信息。这个线程被作为一个关闭钩子添加,当JVM关闭时,这个线程会被执行。
3.完整实现
@Service
public class Executors{
// 在构造方法中声明--项目启动时就会执行
static {
// 声明一个thread,书写具体功能实现
Thread thread = new Thread(() -> {
log.info("JVM接受到关闭命令,等待线程池运行完剩余任务后结束线程池............");
// 请求关闭线程池,不再接受新任务
executorService.shutdown();
try {
// 等待线程池终止,等待最长60秒
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
// 超时后尝试强制关闭
executorService.shutdownNow();
}
} catch (InterruptedException e) {
// 当前线程被中断操作
executorService.shutdownNow();
// 重新中断当前线程
Thread.currentThread().interrupt();
}
log.info("线程池结束成功............");
});
// 声明为用户线程:意味着在JVM中,只要这个线程没有执行完毕,即便是其他用户线程都已经完成,JVM也不会退出
thread.setDaemon(false);
// 当机器被关机时,会执行该线程,直到执行完毕才会关机
Runtime.getRuntime().addShutdownHook(thread);
}
}