今天总结一个设计模式中的属于并发里的一个设计模式,叫两阶段终止模式,看这个名字可能大家都不知道是怎样的,实际上这个就是在考验你对多线程中的interrupt()方法的理解!从而实现线程优雅的退出!本文全是细节,初学者请仔细看,多理解!
来看看jdk手册对于interrupt的介绍:
还是比较抽象!我结合自己的理解说一说:
Interrupt 这个词很容易让人产生误解。从字面意思来看,好像是说一个线程运行到一半,把它中断了,然后抛出了 InterruptedException 异常,其实并不是!请看下面这段代码:
public class Demo {
public static void main(String[] args) throws InterruptedException {
// 创建一个线程 ---我用简洁写法,Thread里面是Runnable接口(是一个函数式接口)的lambda表达式
Thread t1 = new Thread(()->{
while(true){
System.out.println(Thread.currentThread().getName() + "线程运行中...");
}
},"t1");
t1.start();
// 主线程
Thread.sleep(1000); // 让主线程停一会,使得t1线程执行一会
t1.interrupt();
System.out.println("interrupt执行啦!");
}
}
上面这个代码主线程调用interrupt方法并不会使得线程停止,也没有抛出异常!那么调用这个方法后就什么都没有干嘛,其实不是,它会将打断标记置为true,凭借此打断标记可通过代码判断从而使得线程停止!
public class Demo {
public static void main(String[] args) throws InterruptedException {
// 创建一个线程 ---我用简洁写法,Thread里面是Runnable接口(是一个函数式接口)的lambda表达式
Thread t1 = new Thread(()->{
while(true){
System.out.println(Thread.currentThread().getName() + "线程运行中...,打断标记为:"+ Thread.currentThread().isInterrupted());
}
},"t1");
t1.start();
// 主线程
Thread.sleep(500); // 让主线程停一会,使得t1线程执行一会
t1.interrupt();
System.out.println("interrupt执行啦!");
}
}
使用isInterrupted()就可以判断输出线程的打断状态,可以在控制台很清晰的看到,当t1.interrupt()执行后,打断状态从false变为true!
那什么情况下才会抛出InterruptedException异常呢!正如jdk文档中说的,只有sleep,wait,join这三个轻量级阻塞(可以被中断的阻塞,即唤醒线程)声明了InterruptedException异常,可以抛出InterruptedException异常!
这也间接说明了interrupt的另一个作用,唤醒阻塞的线程,使得它不再阻塞,转而抛出InterruptedException异常,使得线程顺利执行或者顺利执行完毕,让出cpu的时间片!
我们对上面案例代码进行改变:
public class Demo {
public static void main(String[] args) throws InterruptedException {
// 创建一个线程 ---我用简洁写法,Thread里面是Runnable接口(是一个函数式接口)的lambda表达式
Thread t1 = new Thread(()->{
while(true){
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "线程运行中...,打断标记为:"+ Thread.currentThread().isInterrupted());
} catch (InterruptedException e) {
e.printStackTrace();
// 输出在sleep状态被打断后的打断标记
System.out.println(Thread.currentThread().isInterrupted());
}
}
},"t1");
t1.start();
// 主线程
Thread.sleep(2000); // 让主线程停一会,使得t1线程执行一会
t1.interrupt();
System.out.println("interrupt执行啦!");
}
}
可以看到抛出的异常显示t1线程是在睡眠过程中被打断的,抛出了异常,即唤醒了阻塞线程,使得其继续执行,特别需要注意的是,抛出异常后他的打断标记会重置为false,并不是想像中的true!
interrupted方法是Thread的静态方法,这里就不看jdk文档了,可能读不明白,还会产生误导!
这两个函数都是线程用来判断自己是否收到过中断信号的,前者是非静态函数,后者是静态函数。二者的区别在于,前者只是读取中断状态,不修改状态;后者不仅读取中断状态,还会重置中断标志位!
看下面这个例子:
public class Demo {
public static void main(String[] args) throws InterruptedException {
// 创建一个线程 ---我用简洁写法,Thread里面是Runnable接口(是一个函数式接口)的lambda表达式
Thread t1 = new Thread(()->{
while(true){
System.out.println(Thread.currentThread().getName() + "线程运行中...,打断标记为:"+ Thread.interrupted());
}
},"t1");
t1.start();
// 主线程
Thread.sleep(500); // 让主线程停一会,使得t1线程执行一会
t1.interrupt();
System.out.println("interrupt执行啦!");
}
}
当执行t1.interrupt();后,t1线程打断状态本应该转变为true,但由于调用了Thread.interrupted(),重置了打断状态,因此打断状态一直输出false!
先来看看两阶段准直模式是啥:
两阶段终止模式是一种并发设计模式,它用于优雅地终止线程。它将终止过程分成两个阶段,第一阶段由线程T1向线程T2发送终止指令,第二阶段是由线程T2响应终止指令。这种模式通过将停止线程这个动作分解为准备阶段和执行阶段这两个阶段,提供了一种通用的用于优雅地停止线程的方法!
所以说,两阶段终止模式的目的是优雅地终止线程,很多人就会产生疑问,不就是停止线程吗,我stop不行吗,为啥要用这模式优雅的停止?
这里主要还是线程安全的问题,相信都看过stop等方法不建议用吧,强行停止线程可能会生成死锁等线程安全问题(停止了,锁未释放,其他线程拿不到锁),优雅地终止线程可以确保线程在终止之前完成它应该完成的任务并执行一些收尾工作,从而保证数据和业务的安全性。如果随意终止线程,可能会导致数据丢失或损坏,或者导致程序运行不稳定。
说了这么多,也看了上面对interupt的介绍,下面就来看看两阶段终止模式的代码实现吧!
package 两阶段终止模式;
/**
* @Author:Aniu
* @Date:2023/4/10 21:38
* @description 并发设计模式-两阶段终止模式-interrupt
*/
public class Demo {
public static void main(String[] args) throws InterruptedException {
TwoStageTermination t1 = new TwoStageTermination();
t1.start();
Thread.sleep(3000);
t1.stop();
}
}
class TwoStageTermination{
private Thread monitor;
// 启动线程
public void start(){
monitor = new Thread(() -> {
while(true){
Thread currentThread = Thread.currentThread();
// 根据打断标记,退出循环,线程结束
if(currentThread.isInterrupted()){ // isInterrupted() 打断标记的状态
System.out.println("打断标记:true, 线程退出!");
break;
}
try {
Thread.sleep(1000); // 情况一:睡眠中打断,抛出InterruptedException异常,唤醒线程,清除打断标记:false,需要手动重置打断标记为true
System.out.println("线程运行中···"); // 情况二:线程正常运行,打断后,线程不会自动停止,打断标记置为:true,用打断标记写if判断
} catch (InterruptedException e) {
e.printStackTrace();
currentThread.interrupt(); // 再次打断:重置打断标记为true,使得循环退出
}
}
});
monitor.start();
}
// 打断线程
public void stop(){
monitor.interrupt(); // interrupt() 打断线程
}
}
上面有两种情况,一种是在睡眠中打断退出,一种是在运行中打断退出,结合isInterrupted方法实现了线程的优雅退出!
如果你觉得博主写的还不错的话,可以关注一下当前专栏,博主会更完这个系列的哦!也欢迎订阅博主的其他好的专栏。
系列专栏
flask框架入门到实战
软磨 css
硬泡 javascript