优雅终止线程的设计模式

一、两阶段终止

两阶段终止(Two-phase Termination)模式是一种用于优雅终止线程的设计模式。该模式的基本思想是通过两个阶段来终止线程:

  1. 第一个阶段是发送终止请求
  2. 第二个阶段是等待线程终止

Java线程进入终止状态的前提是线程进入RUNNABLE状态,而实际上线程也可能处在休眠状态,也就是说,我们想要终止一个线程,首先要把线程的状态从休眠状态转换到RUNNABLE状态。利用Java线程中断机制的interrup()方法,可以让线程从休眠状态转换到RUNNABLE状态。

思考:第二阶段,线程转换到RUNNABLE状态之后,我们如何再将其终止呢?

RUNNABLE状态转换到终止状态,优雅的方式是让Java线程自己执行完run()方法,所以一般我们采用的方法是设置一个标志位,然后线程会在合适的时机检查这个标志位,如果发现符合终止条件,则自动退出run()方法。

综合上面这两点,我们能总结出终止指令,其实包括两方面内容:interrupt()方法和线程终止的标志位。

两阶段终止模式可以带来多种好处,例如:

  1. 优雅终止:两阶段终止模式可以优雅地终止线程,避免突然终止线程带来的副作用;
  2. 安全性:两阶段终止模式可以在线程终止前执行必要的清理工作,以确保程序的安全性和稳定性;
  3. 灵活性:两阶段终止模式可以根据具体情况灵活的设置终止条件和清理工作。

二、使用场景

1. 利用两阶段终止模式设计优雅终止监控操作

在多线程程序中,如果有一些线程需要执行长时间的监控或者轮询操作,可以使用两阶段终止模式来终止这些线程的执行。使用两阶段终止模式可以保证监控线程在执行终止操作时能够安全地释放资源和退出线程。同时,该模式还可以保证监控线程在终止前能够完成必要的清理工作,从而避免资源泄漏和其他问题。

方式一:通过自定义的中断标志来实现

java 复制代码
/**
 * 用两阶段终止模式终止监控操作
 */
public class MonitorThread extends Thread {

    // 在监控线程中添加一个volatile类型的标志变量,用于标识是否需要终止线程的执行
    private volatile boolean terminated = false;

    @Override
    public void run() {
        while (!terminated) {
            // 执行监控操作
            monitorOperation();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 执行清理操作
        System.out.println("监控线程正在执行清理操作...");
        releaseResources();
    }

    private void terminate() {
        // 设置标志变量为true,并等待一段时间
        terminated = true;
        try {
            // 等待5秒,期间监控线程会检查terminated的状态
            join(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void releaseResources() {
        System.out.println("监控线程正在释放资源和进行必要的清理工作...");
    }

    private void monitorOperation() {
        System.out.println("监控线程正在执行监控操作...");
    }

    public static void main(String[] args) throws InterruptedException {
        MonitorThread thread = new MonitorThread();
        // 启动监控线程
        thread.start();
        // 模拟在10秒后终止监控线程,主线程休眠期间,监控线程在执行监控操作
        Thread.sleep(10000);
        // 终止监控线程
        thread.terminate();

        // 主线程休眠100秒,等待监控线程执行清理操作
        Thread.sleep(100000);
    }
}

运行结果:

java 复制代码
监控线程正在执行监控操作...
监控线程正在执行监控操作...
监控线程正在执行监控操作...
监控线程正在执行监控操作...
监控线程正在执行监控操作...
监控线程正在执行监控操作...
监控线程正在执行监控操作...
监控线程正在执行监控操作...
监控线程正在执行监控操作...
监控线程正在执行监控操作...
监控线程正在执行清理操作...
监控线程正在释放资源和进行必要的清理工作...

方式二:使用interrupt()的中断标志 + 自定义的中断标志

java 复制代码
/**
 * 用两阶段终止模式终止监控操作
 */
public class MonitorThread2 extends Thread {

    // 在监控线程中添加一个volatile类型的标志变量,用于标识是否需要终止线程的执行
    private volatile boolean terminated = false;

    @Override
    public void run() {
        while (!Thread.interrupted()&&!terminated) {
            // 执行监控操作
            monitorOperation();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("监控线程被中断,准备退出...");
                Thread.currentThread().interrupt();
                e.printStackTrace();
            }
        }

        // 执行清理操作
        System.out.println("监控线程正在执行清理操作...");
        releaseResources();
    }

    private void terminate() {
        // 设置标志变量为true,并等待一段时间
        terminated = true;
        try {
            // 等待5秒,期间监控线程会检查terminated的状态
            join(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void releaseResources() {
        System.out.println("监控线程正在释放资源和进行必要的清理工作...");
    }

    private void monitorOperation() {
        System.out.println("监控线程正在执行监控操作...");
    }

    public static void main(String[] args) throws InterruptedException {
        MonitorThread2 thread = new MonitorThread2();
        // 启动监控线程
        thread.start();
        // 模拟在10秒后终止监控线程,主线程休眠期间,监控线程在执行监控操作
        Thread.sleep(10000);
        // 为监控线程设置中断标志位
        thread.interrupt();

        // 主线程休眠100秒,等待监控线程执行清理操作
        Thread.sleep(100000);
    }
}

运行结果:

java 复制代码
监控线程正在执行监控操作...
监控线程正在执行监控操作...
监控线程正在执行监控操作...
监控线程正在执行监控操作...
监控线程正在执行监控操作...
监控线程正在执行监控操作...
监控线程正在执行监控操作...
监控线程正在执行监控操作...
监控线程正在执行监控操作...
监控线程正在执行监控操作...
监控线程被中断,准备退出...
监控线程正在执行清理操作...
监控线程正在释放资源和进行必要的清理工作...
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at MonitorThread2.run(MonitorThread2.java:15)

2. 如何优雅的终止线程池

Java领域用的最多的还是线程池,而不是手动地创建线程。那我们如何优雅地终止线程池呢?线程池提供了两个终止线程池的方法:

  • shutdown()方法会停止线程池接受新的任务,并等待线程池中的所有任务执行完毕,然后关闭线程池。在调用shutdown()方法后,线程池不再接受新的任务,但是会将任务队列中的任务继续执行直到队列为空。如果线程池中的任务正在执行,但是还没有执行完毕,线程池会等待所有任务执行完毕后再关闭线程池。
  • shutdownNow()方法会停止线程池接受新的任务,并尝试中断正在执行任务的线程,然后关闭线程池。在调用shutdownNow()方法后,线程池不再接受新的任务,同时会中断正在执行任务的线程并返回一个未执行的任务列表。该方法会调用每个任务的interrupt()方法尝试中断执行任务的线程,但是并不能保证线程一定会被中断,因为线程可以选择忽略中断请求。

使用shutdown()方法终止线程池:

java 复制代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

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();
    }
}

执行结果:

java 复制代码
pool-1-thread-1正在执行任务...
pool-1-thread-2正在执行任务...
pool-1-thread-5正在执行任务...
pool-1-thread-3正在执行任务...
pool-1-thread-4正在执行任务...
pool-1-thread-1任务执行完成
pool-1-thread-4任务执行完成
pool-1-thread-3任务执行完成
pool-1-thread-2任务执行完成
pool-1-thread-5任务执行完成
pool-1-thread-2正在执行任务...
pool-1-thread-1正在执行任务...
pool-1-thread-3正在执行任务...
pool-1-thread-4正在执行任务...
pool-1-thread-5正在执行任务...
pool-1-thread-2任务执行完成
pool-1-thread-5任务执行完成
pool-1-thread-1任务执行完成
pool-1-thread-4任务执行完成
pool-1-thread-3任务执行完成

使用shutdownNow()方法终止线程池:

java 复制代码
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

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() + "任务执行完成");
                }
            });
        }

        // 等待线程池中的任务执行完毕,或者超时时间到达
        boolean terminated = executorService.awaitTermination(3, TimeUnit.SECONDS);
        if (!terminated) {
            // 如果线程池中还有未执行完毕的任务,则调用线程池的shutdownNow()方法,中断所有正在执行的任务
            // 如果有还没开始执行的任务,则返回未执行的任务列表
            List<Runnable> tasks = executorService.shutdownNow();
            System.out.println("剩余未执行的任务数" + tasks.size());
        }
    }
}
java 复制代码
pool-1-thread-1正在执行任务...
pool-1-thread-3正在执行任务...
pool-1-thread-2正在执行任务...
pool-1-thread-4正在执行任务...
pool-1-thread-5正在执行任务...
剩余未执行的任务数5
pool-1-thread-2任务执行完成
pool-1-thread-3任务执行完成
pool-1-thread-1任务执行完成
pool-1-thread-4任务执行完成
pool-1-thread-5任务执行完成
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at ThreadPoolDemo.lambda$main$0(ThreadPoolDemo.java:17) <5 internal lines>
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at ThreadPoolDemo.lambda$main$0(ThreadPoolDemo.java:17) <5 internal lines>
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at ThreadPoolDemo.lambda$main$0(ThreadPoolDemo.java:17) <5 internal lines>
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at ThreadPoolDemo.lambda$main$0(ThreadPoolDemo.java:17) <5 internal lines>
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at ThreadPoolDemo.lambda$main$0(ThreadPoolDemo.java:17) <5 internal lines>

代码含义详解(取自DeepSeek):

java 复制代码
boolean terminated = executorService.awaitTermination(3, TimeUnit.SECONDS);
相关推荐
张涛酱1074563 小时前
Agent Skills 深入解析:构建可插拔的智能体知识体系
spring·设计模式·ai编程
Kel3 小时前
CrewAI v1.14.2 双模式架构深度剖析:当角色协作遇上事件驱动
人工智能·设计模式·架构
wuxinyan1233 小时前
Java面试题48:一文深入了解java设计模式
java·设计模式·面试
CoderMeijun3 小时前
C++ 单例模式:饿汉模式与懒汉模式
c++·单例模式·设计模式·饿汉模式·懒汉模式
han_4 小时前
JavaScript设计模式(十):模板方法模式实现与应用
前端·javascript·设计模式
楼田莉子5 小时前
仿muduo库的高并发服务器——项目基本认识及其相关概念
服务器·数据结构·c++·学习·设计模式
dozenyaoyida6 小时前
嵌入式设计模式之策略模式(2)
经验分享·设计模式·策略模式
妙蛙种子3111 天前
【Java设计模式 | 创建者模式】 原型模式
java·开发语言·后端·设计模式·原型模式