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喷粪的请绕道~~~~~

相关推荐
CoderYanger5 小时前
C.滑动窗口-求子数组个数-越长越合法——2799. 统计完全子数组的数目
java·c语言·开发语言·数据结构·算法·leetcode·职场和发展
C++业余爱好者5 小时前
Java 提供了8种基本数据类型及封装类型介绍
java·开发语言·python
想用offer打牌5 小时前
RocketMQ如何防止消息丢失?
java·后端·架构·开源·rocketmq
皮卡龙5 小时前
Java常用的JSON
java·开发语言·spring boot·json
利刃大大6 小时前
【JavaSE】十三、枚举类Enum && Lambda表达式 && 列表排序常见写法
java·开发语言·枚举·lambda·排序
float_六七6 小时前
Java反射:万能遥控器拆解编程
java·开发语言
han_hanker6 小时前
java 异常类——详解
java·开发语言
源码获取_wx:Fegn08956 小时前
基于springboot + vue健身房管理系统
java·开发语言·前端·vue.js·spring boot·后端·spring
峥嵘life7 小时前
Android16 EDLA 认证测试CTS问题分析解决
android·java·服务器
Mr1ght7 小时前
为什么 InheritableThreadLocal 在 Spring 线程池中“偶尔”能传递变量?——一次线程池上下文传播的误解
java·spring