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