1、优雅终止线程的设计模式
思考:在一个线程 T1 中如何优雅的终止线程 T2?
正确思路:两阶段终止模式
1.1 两阶段终止(Two-phase Termination)模式——优雅的终止线程
两阶段终止(Two-phase Termination)模式是一种用于优雅终止线程的设计模式。该模式的基本思想是通过两个阶段来终止线程,第一个阶段是发送终止请求,第二个阶段是等待线程终止。
思考:第一阶段,在Java中如何发送终止请求?
回顾一下Java线程的生命周期:
Java 线程进入终止状态的前提是线程进入 RUNNABLE 状态,而实际上线程也可能处在休眠状态,也就是说,我们要想终止一个线程,首先要把线程的状态从休眠状态转换到 RUNNABLE 状态。利用java线程中断机制的interrupt() 方法,可以让线程从休眠状态转换到RUNNABLE 状态。
思考:第二阶段,线程转换到 RUNNABLE 状态之后,我们如何再将其终止呢?
RUNNABLE 状态转换到终止状态,优雅的方式是让 Java 线程自己执行完 run() 方法,所以一般我们采用的方法是设置一个标志位,然后线程会在合适的时机检查这个标志位,如果发现符合终止条件,则自动退出 run() 方法。
综合上面这两点,我们能总结出终止指令,其实包括两方面内容:interrupt() 方法和线程终止的标志位。
两阶段终止模式可以带来多种好处,例如:
- 优雅终止:两阶段终止模式可以优雅地终止线程,避免突然终止线程带来的副作用。
- 安全性:两阶段终止模式可以在线程终止前执行必要的清理工作,以确保程序的安全性和稳定性。
- 灵活性:两阶段终止模式可以根据具体情况灵活地设置终止条件和清理工作。
1.2 使用场景
两阶段终止模式适用于需要优雅终止线程的场景,例如:
- 服务器应用程序:在服务器应用程序中,需要处理大量的请求和数据,并且需要在终止时正确地保存和释放资源,以避免数据丢失和资源泄漏。
- 大规模并发系统:在大规模并发系统中,线程数量可能非常多,并且需要在终止时正确地关闭和释放所有的线程和资源。
- 定时任务系统:在定时任务系统中,需要在任务执行完毕后正确地终止任务线程,并清理相关资源。
- 数据处理系统:在数据处理系统中,需要在处理完所有数据后正确地终止线程,并清理相关资源。
- 消息订阅系统:在消息订阅系统中,需要在订阅结束后正确地终止订阅线程,并清理相关资源。
总之,两阶段终止模式适用于需要优雅终止线程的各种场景,可以提高程序的可靠性和可维护性。在应用该模式时,需要注意正确设计终止条件和清理工作,以避免出现线程安全问题和资源泄漏问题。
利用两阶段终止模式设计优雅终止监控操作
在多线程程序中,如果有一些线程需要执行长时间的监控或者轮询操作,可以使用两阶段终止模式来终止这些线程的执行。使用两阶段终止模式可以保证监控线程在执行终止操作时能够安全地释放资源和退出线程。同时,该模式还可以保证监控线程在终止前能够完成必要的清理工作,从而避免资源泄露和其他问题。
public class MonitorThread extends Thread {//在监控线程中添加一个volatile类型的标志变量,用于标识是否需要终止线程的执行private volatile boolean terminated = false;public void run() {while (!terminated) {// 执行监控操作System.out.println("监控线程正在执行监控操作...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}// 执行清理操作System.out.println("监控线程正在执行清理操作...");releaseResources();}public void terminate() {//设置标志变量为true,并等待一段时间terminated = true;try {join(5000); // 等待5秒钟,期间监控线程会检查terminated的状态} catch (InterruptedException e) {e.printStackTrace();}}private void releaseResources() {// 释放资源和进行必要的清理工作System.out.println("监控线程正在释放资源和进行必要的清理工作...");}public static void main(String[] args) throws InterruptedException {MonitorThread thread = new MonitorThread();//启动监控线程thread.start();//主线程休眠期间,监控线程在执行监控操作Thread.sleep(10000);//终止监控线程thread.terminate();Thread.sleep(100000);}
}
在使用两阶段终止模式终止线程的同时,还可以结合中断机制实现更加可靠和灵活的线程终止操作。使用中断机制可以让线程在终止前及时响应中断请求,并退出线程的阻塞状态。同时,还可以通过检查中断状态和标志变量的状态来判断是否需要终止线程的执行,从而实现更加可靠和灵活的线程终止操作。
public class MonitorThread2 extends Thread {//在监控线程中添加一个volatile类型的标志变量,用于标识是否需要终止线程的执行private volatile boolean terminated = false;public void run() {while (!Thread.interrupted()&&!terminated) {// 执行监控操作System.out.println("监控线程正在执行监控操作...");try {Thread.sleep(1000);} catch (InterruptedException e) {System.out.println("监控线程被中断,准备退出...");Thread.currentThread().interrupt();e.printStackTrace();}}// 执行清理操作System.out.println("监控线程正在执行清理操作...");releaseResources();}public void terminate() {//设置标志变量为true,并等待一段时间terminated = true;try {join(5000); // 等待5秒钟,期间监控线程会检查terminated的状态} catch (InterruptedException e) {e.printStackTrace();}}private void releaseResources() {// 释放资源和进行必要的清理工作System.out.println("监控线程正在释放资源和进行必要的清理工作...");}public static void main(String[] args) throws InterruptedException {MonitorThread2 thread = new MonitorThread2();//启动监控线程thread.start();//主线程休眠期间,监控线程在执行监控操作Thread.sleep(10000);//为监控线程设置中断标志位thread.interrupt();//终止监控线程//thread.terminate();Thread.sleep(100000);}
}
如何优雅地终止线程池
Java 领域用的最多的还是线程池,而不是手动地创建线程。那我们该如何优雅地终止线程池呢?
线程池提供了两个终止线程池的方法:
- shutdown()方法会停止线程池接受新的任务,并等待线程池中的所有任务执行完毕
,然后关闭线程池。在调用shutdown()方法后,线程池不再接受新的任务,但是会将任务队列中的任务继续执行直到队列为空。如果线程池中的任务正在执行,但是还没有执行完毕,线程池会等待所有任务执行完毕后再关闭线程池。 - shutdownNow()方法会停止线程池接受新的任务,并尝试中断正在执行任务的线程
,然后关闭线程池。在调用shutdownNow()方法后,线程池不再接受新的任务,同时会中断正在执行任务的线程并返回一个未执行的任务列表。该方法会调用每个任务的interrupt()方法尝试中断任务执行的线程,但是并不能保证线程一定会被中断,因为线程可以选择忽略中断请求。
public class ThreadPoolDemo {public static void main(String[] args) throws InterruptedException {ExecutorService executorService = Executors.newFixedThreadPool(5);for (int i = 0; i < 10; i++) {executorService.submit(() -> {try {// 执行任务操作System.out.println(Thread.currentThread().getName() + "正在执行任务...");Thread.sleep(5000);} catch (InterruptedException e) {// 重新设置中断状态Thread.currentThread().interrupt();e.printStackTrace();} finally {System.out.println(Thread.currentThread().getName() + "任务执行完毕");}});}// 停止线程池接受新的任务,但不能强制停止已经提交的任务executorService.shutdown();// 等待线程池中的任务执行完毕,或者超时时间到达boolean terminated = executorService.awaitTermination(8, TimeUnit.SECONDS);if (!terminated) {// 如果线程池中还有未执行完毕的任务,则调用线程池的shutdownNow方法,中断所有正在执行任务的线程// 如果有还没开始执行的任务,则返回未执行的任务列表List<Runnable> tasks = executorService.shutdownNow();System.out.println("剩余未执行的任务数:" + tasks.size());}}
}
运行该程序,可以看到线程池会依次执行10个任务,然后优雅地终止线程池的执行,中断所有正在执行的任务。
1.3 注意事项
两阶段终止模式是一种应用很广泛的并发设计模式,在 Java 语言中使用两阶段终止模式来优雅地终止线程,需要注意两个关键点:
- 一个是仅检查终止标志位是不够的,因为线程的状态可能处于休眠态;
- 另一个是仅检查线程的中断状态也是不够的,因为我们依赖的第三方类库很可能没有正确处理中断异常,例如第三方类库在捕获到 Thread.sleep() 方法抛出的中断异常后,没有重新设置线程的中断状态,那么就会导致线程不能够正常终止。所以我们可以自定义线程的终止标志位用于终止线程。
当你使用 Java 的线程池来管理线程的时候,需要依赖线程池提供的 shutdown() 和 shutdownNow() 方法来终止线程池。不过在使用时需要注意它们的应用场景,尤其是在使用 shutdownNow() 的时候,一定要谨慎。