两阶段终止模式-设计模式-并发编程(Java)

1 简介

​ 如何停止一个线程呢?首先,Java没有提供直接的API用于停止线 程 1 程^1 1。此外,还有一些额外的细节需要考虑,如停止的线程处于阻塞(如等待锁)或者等待状态(等待其他线程)、尚未处理完的任务等。

  • 1:stop方法用于强制停止线程,可能造成内存泄露、资源占用等问题,已经被废弃。

Two-phrase Termination(两阶段停止)模式通过将停止线程分解为准备和执行两个阶段,提供了一种通用 优 雅 2 优雅^2 2的停止线程的方法。

  1. 准备阶段:该阶段的主要动作是“通知”目标线程(要停止的线程)准备进行停下。
  2. 执行阶段:该阶段主要动作是检查准备阶段设置的线程停止标志和信号,在此基础上决定停止的时机,并进行适当的“清理”操作。
  • 2:优雅是指可以等要停止的线程处理完待处理的任务后在停止,并且做好清理工作,而不是强制停止。

  • 准备阶段解析:“通知”操作同步设置一个标志变量来指示目标线程可以准备停止。但是,由于目标线程可能处于阻塞、等待或者I/O等待状态,即便设置了等待标志,目标线程也无法立即”看到“该标志从而做出相应动作。因此,这一阶段需要通过目标线程的interrupt方法,以期望目标线程通过捕获相关的异常侦测到该方法调用,从而中断其阻塞状态、等待状态。对于能够对interrupt方法调用做出响应的方法(参加表1-1),目标线程代码通过捕获这些方法抛出的InterruptedException来侦测线程停止信号。但也有一些方法(如InputStream.read)并不对interrupt调用做出响应,此时需要我们手工处理,如同步的Socket I/O操作中通过关闭socket,使处于I/O等待的socket抛出java.net.SocketException。

表1-1 能够对 Thread.interrupt方法做出响应的方法

方法(或者类)响应interrupt调用抛出的异常
Object.wait()、Object.wait(long timout)、Object.wait(long timeout, int nanos)InterruptedException
Thread.sleep(long millis)、Thread.sleep(long millis, int nanos)InterruptedException
Thead.join()、Thread.join(long millis)、Thread.join(long millis, int nanos)InterruptedException
java.util.concurrent.BlockingQueue.take()InterruptedException
java.util.concurrent.locks.Lock.lockInterruptibly()InterruptedException
java.nio.channels.InterruptibleChanneljava.nio.channels.CloseByInterruptException

2 Two-phrase Termination架构

2.1 类图

Two-phrase Termination模式类图如下2-1:

在这里插入图片描述

说明:

  • ThreadOwner:目标线程拥有者。Java语言中,并没有线程拥有者的概念,但是线程背后是其要处理的任务或者其所提供的服务,因此我们不能在不知道某个线程具体在做什么的情况下贸然将其停止。一般地,我们可以把线程的创建者视为线程的拥有者,并假定其“知道“目标线程的工作内容,可以安全的停止线程。
  • Terminatable:可以停止线程的接口。其主要方法及职责如下:
    • terminate:请求目标线程停止。
  • AbstractTerminatableThrea:可停止的线程。主要方法和及职责如下:
    • terminate:设置线程停止标志,并发送停止信号给目标线程。
    • doTerminate:留给子类实现停止线程时所需的一些额外操作,如目标线程中包括Socket I/O,子类可以在改方法中关闭Socket以达到快速停止线程,而不会使目标线程等待I/O完成才能检测到线程停止标志。
    • doRun:线程处理逻辑的方法。留给子类实现线程的处理逻辑。相当于Thread.run(),只不过该方法无须关心停在线程的逻辑,因为逻辑已经封装在run方法中。
    • doCleanup:留给子类实现线程停止后的一些清理工作。
  • TerminationToken:线程停止标志。toShutdown用于指示目标线程可以停止。reservations可以用于反映目标线程还有多少未完成的任务,以支持等目标线程处理完其任务后再停止。
  • ConcreteTerminatableThread:由应用自己实现的AbstractTerminatableThead的实现类。该类需要实现其父类中的doRun抽象方法,在其中实现线程的处理逻辑,并根据应用的实际需要覆盖其父类的doTerminate、doCleanup方法。

2.2 准备阶段

准备阶段时序图如下2-2:在这里插入图片描述

步骤描述:

  1. 客户端代码调用线程拥有者的shutdown方法
  2. shutdown方法调用目标线程的terminate方法
  3. terminate方法将terminationToken的toShutdown标志置为true
  4. terminate方法调用由AbstractTerminatableThread子类实现的doTerminate方法,使得子类可以为停止目标线程做一些其他的必要操作。
  5. 若terminationToken的属性reservations的值为0,则表示目标线程没有未完成的任务或者TheadOwner在停止线程时不关心其是否还有未处理的任务。此时,terminate方法会调用目标线程的interrupt方法。
  6. terminate方法调用结束。
  7. shutdown调用返回,此时目标线程可能仍然在运行。

2.3 执行阶段

执行阶段由目标线程的run方法去检查terminationToken的toShutdown属性、reservations属性的值,并捕获由interrupt方法调用抛出的异常以决定是否停止线程。在线程停止前调用由AbstractTerminatableThread子类实现的doCleanup方法做一些清理工作(如果需要)。

3 应用场景模拟

3.1 简单应用的初级实现

场景:某系统的监控功能被封装在一个模块中,改监控功能属于定时执行模块,我们需要一个机制来停止监控执行,比如当系统维护或者升级更新的时候需要停止监控功能。这时,可以使用两阶段停止模式来实现,流程图如下:在这里插入图片描述

代码实现如下:

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

@Slf4j(topic = "c.TwoPhraseTermination")
public class TwoPhraseTermination {
    // 监控线程
    private Thread monitorThread;
    // 结束标记
    private  boolean stop = false;

    // 启动监控线程
    public void start() {
        monitorThread = new Thread(() -> {
            Thread current = Thread.currentThread();
            // 判断是否被打断
            while (true) {
                if (stop) {
                    log.debug("清理工作...");
                    break;
                }

                try {
                    // 工作内容
                    // 模拟花费的时间
                    TimeUnit.SECONDS.sleep(1);
                    log.debug("执行监控记录");
                } catch (InterruptedException e) {
                    // 异常处理
                }
            }
        });

        monitorThread.start();
    }

    // 停止监控线程
    public void stop() {
        stop = true;
        // 如果线程在睡眠,可立即结束线程
        monitorThread.interrupt();
    }
}

测试类如下:

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

@Slf4j(topic = "c.Test01")
public class Test01 {
    public static void main(String[] args) throws InterruptedException {
        TwoPhraseTermination tpt = new TwoPhraseTermination();
        tpt.start();

        TimeUnit.MILLISECONDS.sleep(3500);
        log.debug("停止监控线程...");
        tpt.stop();
    }
}

3.2 实战案例

目前本人还没有在项目中使用,想要了解的可以自行搜索或者参考《Java多线程编程实战指南 设计模式篇》中关于两阶段终止模式的实战案例。

4 总结

❓QQ:806797785

⭐️源代码仓库地址:https://gitee.com/gaogzhen/concurrent

参考:

[1]黄文海.Java多线程编程实战指南(设计模式篇)[M].北京:电子工业出版社,2015.10.

[2]黑马程序员.黑马程序员深入学习Java并发编程,JUC并发编程全套教程[CP/OL].2020-01-18/2022-10-02.p37~p40.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gaog2zh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值