两阶段终止模式 (Two-Phase Termination Pattern)

两阶段终止模式 (Two-Phase Termination Pattern) 指的是当希望结束一个线程时,发出终止请求后线程不会立即停止,而是先完成当前的刷新或清理工作。线程进入“终止处理中”状态,在该状态下不再执行日常工作任务,而是专注于执行终止相关的操作。

设计考量

采用该模式主要基于以下三个关键因素:

  1. 安全结束线程
    必须确保线程能够安全地结束。直接使用 Thread.stop() 方法存在风险,因为它会强制停止线程,无论线程执行到哪一行代码,都无法保证资源释放或数据一致性,可能导致对象处于不一致状态。
  2. 正常的终止处理
    必须确保能够进行正常的收尾工作。在 Java 中,这一点通常可以使用 finally 代码块来实现,确保无论何种情况下退出循环,清理逻辑都能被执行。
  3. 高响应性的终止
    收到终止请求后,如果线程正处于 waitsleepjoin 等阻塞状态,不应等到超时才终止,而应立即中断这些状态,进而执行终止操作。

工作流程

当一个线程正在执行周期性工作时,若在“作业阶段”收到了停止请求,该线程不应立即离开,而应先完成本次周期内部的工作,然后进入“善后阶段”完成清理工作。

所谓的两阶段终止,即中止“运作阶段”,并完成“善后阶段”,从而完整地结束线程的工作生命周期。

代码实现

两阶段终止线程的架构模式如下:

public class WorkerTerminalThread extends Thread {
    // 已经送出终止请求为 true,初始化的时候为 false
    // 由于该字段可能会被多个线程访问修改,为了保护就使用 volatile
    private volatile boolean shutdownRequested = false;

    // 终止请求
    public void shutdownRequest() {
        shutdownRequested = true;
        interrupt();
    }

    public boolean isShutdownRequest() {
        return shutdownRequested;
    }

    // 具体动作
    public final void run() {
        try {
            while (!shutdownRequested) {
                doWork();
            }
        } catch (InterruptedException e) {
            // 捕获中断异常,配合标志位退出循环
        } finally {
            // 终止处理中的工作,不会进行平常操作,但是会进行终止处理
            doShutdown();
        }
    }

    // 具体工作操作
    private void doWork() throws InterruptedException {
        // 模拟周期性工作
    }

    // 终止后进行善后处理
    private void doShutdown() {
        // 清理资源
    }
}

原理解析

  1. Volatile 的作用
    利用 volatile 修饰 shutdownRequested 字段,是因为该字段可能会被多个线程使用及修改。为了保证该字段的可见性,可以使用同步方法、同步代码块或 volatile 关键字。
    使用 volatile 修饰的字段,强制该成员变量在每次被线程访问时,都从共享内存中重读该成员变量的值;而且当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
  2. 标识位与中断状态的结合
    这里运用了“标识位”和“中断状态”组合的方式来终止线程,之所以不单独使用其中一种,原因如下:

    • 仅利用标识位:无法使那些处于 waitsleep 或者 join 中的线程马上停止,响应性会很差。
    • 仅利用中断 (interrupt)interrupt 仅仅对于 waitsleepjoin 处抛出异常。如果工作代码执行在 catch 里捕获了 InterruptedException 后未做处理,则此时中断标志可能被清除,导致中断不起作用。
    • 组合使用:加入 interrupt 后可以立刻使得处于阻塞状态的线程中断并抛出异常;配合 shutdownRequested 标识位,确保即使在异常被捕获的情况下,也能通过检查标志位正常退出循环。
说明:本文示例基于经典 Java 多线程模型编写。在现代 Java 并发编程中,更推荐使用 ExecutorService 配合 RunnableCallable 任务,并通过 shutdown()Future.cancel() 来管理线程生命周期,而非直接继承 Thread 类。但两阶段终止的核心思想(请求 + 清理)依然适用。