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

相关推荐
斌斌_____11 分钟前
Spring Boot 配置文件的加载顺序
java·spring boot·后端
路在脚下@20 分钟前
Spring如何处理循环依赖
java·后端·spring
一个不秃头的 程序员42 分钟前
代码加入SFTP JAVA ---(小白篇3)
java·python·github
丁总学Java1 小时前
--spring.profiles.active=prod
java·spring
上等猿1 小时前
集合stream
java
java1234_小锋1 小时前
MyBatis如何处理延迟加载?
java·开发语言
菠萝咕噜肉i1 小时前
MyBatis是什么?为什么有全自动ORM框架还是MyBatis比较受欢迎?
java·mybatis·框架·半自动
林的快手1 小时前
209.长度最小的子数组
java·数据结构·数据库·python·算法·leetcode
向阳12182 小时前
mybatis 缓存
java·缓存·mybatis
上等猿2 小时前
函数式编程&Lambda表达式
java