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

相关推荐
魔道不误砍柴功2 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2342 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨2 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
测开小菜鸟4 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity5 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天5 小时前
java的threadlocal为何内存泄漏
java
caridle5 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
^velpro^5 小时前
数据库连接池的创建
java·开发语言·数据库
苹果醋35 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx
秋の花5 小时前
【JAVA基础】Java集合基础
java·开发语言·windows