JUC之-两阶段终止:如何优雅的停掉线程

试想这样一种场景,某天程序猿张三正在美滋滋下载电影,但是突然收到条短信,提示手机流量已经用完了,再下载就该用软妹币的力量来下载了,这时候张三该干些啥呢?自然是点击停止下载,及时止损。像这样点个按钮,就能让一个线程停止的操作该咋实现呢?其中一个方案就是线程两阶段终止模式

1、啥叫两阶段终止?

简单的理解就是,当一个线程在运行的时候,另一个线程能够通过一种机制使正在运行的线程停止,并在停止前做一些事情,比如关闭资源、备份数据等操作。

假设没有两阶段终止,试想以下场景: 张三美滋滋的下载个电影,下载到90%了,APP发现张三没充VIP,突然调了个 Thread.stop()方法。张三傻眼了,那能咋办呢,只能再充个会员呗。但是充完后发现,APP并没有保存原本的下载进度(因为 stop() 方法很暴力),张三只能苦逼的重新下载一遍。

上面的例子中,下载线程被终止时,由于 Thread.stop() 是一个很暴力的方法,直接让线程终止。下载线程来不及记录下载进度,进度自然就丢了。

那这种情况该如何进行避免呢?以下是一个两阶段终止的例子:

2、两阶段终止

使用 interrupt 方法,interrupt 可以打断一个线程,本文暂时只讨论两种情况:

  • 打断正在休眠的线程,会抛出 InterruptedException,并将打断标记设置为 false
  • 打断正在运行的线程,会将打断标记设置为 true,并且是否终止线程由线程逻辑决定。

2.1 打断正在休眠的线程

代码示例: 线程 t1 运行后进入休眠状态,1秒后主线程打断 t1 线程

java 复制代码
public static void main(String[] args) {

    Thread t1 = new Thread(() -> {
        log.debug("start running...");
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            log.error("InterruptedException");
        }
        log.debug("finish, {}", Thread.currentThread().isInterrupted());
    }, "t1");
    t1.start();
    Sleeper.sleep(TimeUnit.SECONDS, 1);
    log.debug("interrupt!!!!!");
    t1.interrupt();
     Sleeper.sleepMs(10);
    log.debug("t1 {}", t1.isInterrupted());

}

程序运行结果为:

2.2 打断正在运行的线程

代码示例: 打断正在运行的线程,线程的 interrupted 会被置为 true,是否对线程进行终止由程序自身决定

java 复制代码
public static void main(String[] args) {

    Thread t1 = new Thread(() -> {
        log.debug("t1, start...");
        while (true) {
            boolean interrupted = Thread.currentThread().isInterrupted();
            if (interrupted) {
                log.debug("被打断,停止执行...");
                break;
            }
        }
        log.debug("t1 finish...");
    }, "t1");
    t1.start();

    Sleeper.sleep(TimeUnit.SECONDS, 1);
    log.debug("interrupt!!!!!");
    t1.interrupt();

}

程序运行结果为:

2.3 两阶段终止

两阶段终止的原理其实就是 interrupt 方法。t1 线程正在运行过程中,主线程可以调用 t1 的interrupt()方法来进行打断,以下用下载电影的例子来说明:

java 复制代码
public static void main(String[] args) throws InterruptedException {

    Thread downloadThread = new Thread(() -> {
        int progress = 0;
        while (true) {
            if (Thread.currentThread().isInterrupted()) {
                log.debug("用户点了停止按钮,保存一下进度再停机,当前进度为:{}%", progress);
                break;
            }
            try {
                TimeUnit.SECONDS.sleep(1);
                log.debug("下载电影中,当前进度为:{}%", ++progress);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }, "download-task");

    log.debug("开始下载电影...");
    downloadThread.start();
    TimeUnit.SECONDS.sleep(10);
    log.debug("没流量了,赶紧暂停!!!");
    downloadThread.interrupt();
}

执行结果:

其中,downloadThread 为工作线程,正在不断地下载电影,主线程过了10秒钟,突然发现手机流量不够了,就想着把 downloadThread 线程赶紧停下来,此时就可以调用 downloadThread 线程的 interrupt 方法。

调用 downloadThread 线程的 interrupt 方法过后,downloadThread 的 isInterrupted 就会被设置为 true,当再次运行到下载时,发现 isInterrupted 为 true 了,那就说明有线程打断下载了,此时就应该做一些善后的事情(保存下载进度),做完后就可以结束线程了。

这种方式可以很优雅的将一个运行中的线程进行终止,但是在以上的例子中,有两个位置可以被打断:

  • A: TimeUnit.SECONDS.sleep(1)
  • B: log.debug("下载电影中,当前进度为:{}%", ++progress);

这两行代码都可以被打断,其实也就对应着 2.1、2.2 中两种情形,分别是运行期间被打断以及休眠期间被打断。

  • 运行期间被打断:那么 downloadThread 线程的 isInterrupted 会被设置为 true,下次循环时,就可以退出循环
  • 休眠期间被打断,那么会抛出 InterruptedException,并且将 isInterrupted 设置为 false,代码会进入第14行的 catch 块中。因此,必须在 catch 块中再调用一次 interrupt 方法,否则线程的 isInterrupted 一直是false,线程就该长生不老了。

以上就是两阶段终止的那点事儿,如有错误,欢迎指正。装X喷粪的请绕道~~~~~

相关推荐
毕设源码-朱学姐21 小时前
【开题答辩全过程】以 公务员考试在线测试系统为例,包含答辩的问题和答案
java
serendipity_hky21 小时前
【SpringCloud | 第2篇】OpenFeign远程调用
java·后端·spring·spring cloud·openfeign
RwTo21 小时前
【源码】-Java线程池ThreadPool
java·开发语言
SadSunset21 小时前
(15)抽象工厂模式(了解)
java·笔记·后端·spring·抽象工厂模式
兮动人21 小时前
EMT4J定制规则版:Java 8→17迁移兼容性检测与规则优化实战
java·开发语言·emt4j
一点★21 小时前
Java中的常量池和字符串常量池
java·开发语言
问君能有几多愁~21 小时前
C++ 日志实现
java·前端·c++
菜鸟plus+1 天前
Java 接口的演变
java·开发语言
李慕婉学姐1 天前
【开题答辩过程】以《基于springboot的地铁综合服务管理系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·spring boot·后端
IT空门:门主1 天前
Spring AI的教程,持续更新......
java·人工智能·spring·spring ai