线程中断与LockSupport
创始人
2025-05-29 09:46:17
0

文章目录

  • 1、线程中断机制
    • 1.1、什么是中断机制?
    • 1.2、中断的相关API方法之三大方法说明
      • 如何使用中断标识停止线程?
        • ① 通过一个volatile变量实现
        • ② 通过AtomicBoolean(原子布尔型)
        • ③通过Thread类自带的中断api方法实现
        • API源码分析
      • 当前线程的中断标识为true,是不是线程就立刻停止?
      • 静态方法Thread.interrupted(),谈谈你的理解
  • 2、LockSupport
  • 3、线程等待唤醒机制
    • 3种让线程等待和唤醒的方法
      • ①Object类中的wait和notify方法实现线程等待和唤醒
        • 小总结
      • ②Condition接口中的await后signal方法实现线程的等待和唤醒
        • 小总结
        • Object和Condition使用的限制
      • ③LockSupport类中的park等待和unpark唤醒
        • 阻塞
        • 唤醒
        • 小总结
      • 为什么LockSupport可以突破wait/notify的原有调用顺序?
      • 为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?

1、线程中断机制

1.1、什么是中断机制?

  • 首先
    一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止

    所以,Thread.stop, Thread.suspend, Thread.resume 都已经被废弃了。

  • 其次
    在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。

解决方案:

  • Java提供了一种用于停止线程的协商机制——中断。

  • 中断只是一种协作协商机制,Java没有给中断增加任何语法,中断的过程完全需要自己实现。

  • 若要中断一个线程,需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设成true

  • 接着需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程要求这条线程中断,

每个线程对象中都有一个标识,用于表示线程是否被中断;该标识位为true表示中断,为false表示未中断;通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。

什么?不好理解?

举个栗子:顾客(线程a)在无烟餐厅中吸烟,服务员(线程b)希望他别吸烟了,不是强行停止他吸烟(让其他线程强制中断或停止),而是给他的标志位打为true(协商机制),具体的停止吸烟还是要顾客自己停止。(体现了协商机制)

1.2、中断的相关API方法之三大方法说明

方法说明
public void interrupt()实例方法,实例方法interrupt()仅仅是设置线程的中断状态为true,发起一个协商而不会立刻停止线程
public static boolean interrupted()静态方法,Thread.interrupted();判断线程是否被中断,并清除当前中断状态。这个方法做了两件事:1 返回当前线程的中断状态;2 将当前线程的中断状态设为false(这个方法有点不好理解,因为连续调用两次的结果可能不一样。)
public boolean isInterrupted()实例方法,判断当前线程是否被中断(通过检查中断标志位)

如何使用中断标识停止线程?

① 通过一个volatile变量实现

  • volatile保证了可见性,t2修改了标志位后能马上被t1看到
public class interruptDemo {static volatile boolean isStop = false;public static void main(String[] args) {new Thread(()->{while(true){if(isStop){//如果这个标志位被其他线程改为true了System.out.println(Thread.currentThread().getName()+"\t isStop被修改为true,程序终止");break;}System.out.println("t1 ------hello volatile");//----------------------如果没停止,那就一直打印}},"t1").start();try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{isStop = true;},"t2").start();}
}
//-----------------------------------------------------------------------------------------------------
//t1 ------hello volatile
//t1 ------hello volatile
//t1 ------hello volatile
//t1 ------hello volatile
//t1 ------hello volatile
//t1 ------hello volatile
//t1 ------hello volatile
//t1   isStop被修改为true,程序终止

② 通过AtomicBoolean(原子布尔型)

public class interruptDemo {static AtomicBoolean atomicBoolean = new AtomicBoolean(false);public static void main(String[] args) {m1_volatile();}public static void m1_volatile() {new Thread(()->{while(true){if(atomicBoolean.get()){//如果这个标志位被其他线程改为true了System.out.println(Thread.currentThread().getName()+"\t isStop被修改为true,程序终止");break;}System.out.println("t1 ------hello volatile");//----------------------如果没停止,那就一直打印}},"t1").start();try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{atomicBoolean.set(true);},"t2").start();}
}
----------------------------------------------------------------------------------------------------
//t1 ------hello volatile
//t1 ------hello volatile
//t1 ------hello volatile
//t1 ------hello volatile
//t1 ------hello volatile
//t1 ------hello volatile
//t1   isStop被修改为true,程序终止

③通过Thread类自带的中断api方法实现

//默认的中断标志位是false,然后被改为了true
public static void main(String[] args) {m1_volatile();
}public static void m1_volatile() {Thread t1 = new Thread(() -> {while (true) {if (Thread.currentThread().isInterrupted()) {//一旦发现中断标志位被修改System.out.println(Thread.currentThread().getName() + "\t isInterrupted()被修改为true,程序终止");break;}System.out.println("t1 ------hello interrupt ");//----------------------如果没停止,那就一直打印}}, "t1");t1.start();try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{t1.interrupt();//把t1中断},"t2").start();
}
---------------------------------------------------------------------------------------------------
//t1 ------hello interrupt 
//t1 ------hello interrupt 
//t1 ------hello interrupt 
//t1 ------hello interrupt 
//t1 ------hello interrupt 
//t1 ------hello interrupt 
//t1 ------hello interrupt 
//t1 ------hello interrupt 
//t1 ------hello interrupt 
//t1   isInterrupted()被修改为true,程序终止

API源码分析

实例方法interrupt()
在这里插入图片描述

在这里插入图片描述

实例方法isInterrupted,返回布尔值

在这里插入图片描述
在这里插入图片描述

说明

  • 具体来说,当对一个线程,调用 interrupt() 时:
    • 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。所以, interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。
    • 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态(中断状态将被清除),并抛出一个InterruptedException异常。
    • 中断不活动的线程不会产生任何影响

当前线程的中断标识为true,是不是线程就立刻停止?

当然不是,仅仅只是设置了一个中断状态

举个栗子:

看看中断 是否会立即停止这个300的线程

public class InterruptDemo02 {public static void main(String[] args) {Thread t1 = new Thread(()->{for(int i = 0;i < 300;i ++){System.out.println("---------" + i);}System.out.println("after t1.interrupt()---第2次----"+Thread.currentThread().isInterrupted());},"t1");t1.start();System.out.println("before t1.interrupt()----"+t1.isInterrupted());t1.interrupt();try {TimeUnit.MILLISECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("after t1.interrupt()---第1次---"+t1.isInterrupted());try {TimeUnit.MILLISECONDS.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("after t1.interrupt()---第3次---"+t1.isInterrupted());}
}
------------------------------------------------------------------------------------------------------
//before t1.interrupt()----false
//---------0
//---------1
//---------2
//---------3
//....
//---------136
//after t1.interrupt()---第1次---true    ------此处中断标志位设置为了true,但是t1仍然在运行
//---------137
//---------298
//---------299
//after t1.interrupt()---第2次----true
//after t1.interrupt()---第3次---false//中断不活动的线程不会产生任何影响,线程结束后应该是自动变为了false

可以看到,虽然中断标志位变了。但是 i 一直在循环

在我们基本中断程序的骨架上 + 一个sleep阻塞 ,会发现 中断异常且导致程序无限循环

public class InterruptDemo03 {public static void main(String[] args) {Thread t1 =  new Thread(()->{while(true){if(Thread.currentThread().isInterrupted()){System.out.println(Thread.currentThread().getName()+"\t"+"中断标志位:"+Thread.currentThread().isInterrupted()+"程序终止");break;}try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();// Thread.currentThread().interrupt();  假如加了这个,程序可以终止,只会爆异常}System.out.println("-----hello InterruptDemo03");}},"t1");t1.start();try {TimeUnit.MILLISECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> t1.interrupt()).start();}
}
//报异常了,并且程序一直在跑
//java.lang.InterruptedException: sleep interrupted
//  at java.lang.Thread.sleep(Native Method)
//-----hello InterruptDemo03
//-----hello InterruptDemo03
//-----hello InterruptDemo03
//......
//--------------------------------------------------------------------------------------
//---------加了Thread.currentThread().interrupt();
//java.lang.InterruptedException: sleep interrupted
// at java.lang.Thread.sleep(Native Method)
//  at com.zhang.admin.controller.InterruptDemo03.lambda$main$0(InterruptDemo03.java:15)
//  at java.lang.Thread.run(Thread.java:748)
//-----hello InterruptDemo03
//t1  中断标志位:true程序终止

那为什么会出现这种结果呢?

还记得之前介绍过

如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态(中断状态将被清除),并抛出一个InterruptedException异常。

总结:

  • 1、中断标志位 默认是false
  • 2 、t2 ----->t1发出了中断协商,t2调用 t1.interrupt(),中断标志位true
  • 3、 中断标志位true,正常情况下,程序停止,-
  • 4 、中断标志位true,异常情况下(sleep, wait, join 等状态),报 InterruptedException,将会把中断状态清除。中断标志位false导致无限循环。
  • 5、 在catch块中,需要再次给中断标志位设置为true,2次调用,线程才会停止

sleep方法抛出InterruptedException后,中断标识也被清空置为false,我们在catch通过Thread.currentThread().interrupt()方法再次将中断标志设置为true,这就导致不会无限循环了

静态方法Thread.interrupted(),谈谈你的理解

  • public static boolean interrupted()
  • 静态方法,Thread.interrupted();判断线程是否被中断,并清除当前中断状态这个方法做了两件事:
    • 返回当前线程的中断状态
    • 将当前线程的中断状态设为false(这个方法有点不好理解,因为连续调用两次的结果可能不一样。
public class InterruptDemo04 {public static void main(String[] args) {System.out.println(Thread.currentThread().getName()+"\t"+Thread.interrupted());System.out.println(Thread.currentThread().getName()+"\t"+Thread.interrupted());System.out.println("-----1");Thread.currentThread().interrupt();//中断标志位设置为trueSystem.out.println("-----2");System.out.println(Thread.currentThread().getName()+"\t"+Thread.interrupted());System.out.println(Thread.currentThread().getName()+"\t"+Thread.interrupted());}
}
//main  false
//main  false
//-----1
//-----2
//main  true
//main  false

看下源码,interrupted()对比isInterrupted()

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

可以看出:

  • 他们在底层都调用了native方法 isInterrupted。

  • 传入参数ClearInterrupted一个传参传了 true,一个传了 false。

  • 静态方法interrupted() 中true表示清空当前中断状态;实例方法 isInterrupted 则不会。

2、LockSupport

用于创建锁和其他同步类的基本线程阻塞原语

一句话:

核心就是park()unpark()方法

  • park()方法是阻塞线程
  • unpark()方法是解除阻塞线程

3、线程等待唤醒机制

3种让线程等待和唤醒的方法

  1. 使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程
  2. 使用 JUC 包中Conditionawait()方法让线程等待,使用signal()方法唤醒线程
  3. LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程

①Object类中的wait和notify方法实现线程等待和唤醒

  • 正常情况:
public class LockSupportDemo
{public static void main(String[] args){Object objectLock = new Object();new Thread(() -> {synchronized (objectLock) {System.out.println(Thread.currentThread().getName()+"\t ---- come in");try {objectLock.wait();//----------------------这里先让他等待} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+"\t"+"---被唤醒了");},"t1").start();//暂停几秒钟线程try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); }new Thread(() -> {synchronized (objectLock) {objectLock.notify();//-------------------------再唤醒它System.out.println(Thread.currentThread().getName()+"\t ---发出通知");}},"t2").start();}
}
//t1   ---- come in
//t2   ---发出通知
//t1  ---被唤醒了
  • 异常1—去掉synchronized
public class LockSupportDemo
{public static void main(String[] args){Object objectLock = new Object();new Thread(() -> {//    synchronized (objectLock) {System.out.println(Thread.currentThread().getName()+"\t ---- come in");try {objectLock.wait();} catch (InterruptedException e) {e.printStackTrace();}//      }System.out.println(Thread.currentThread().getName()+"\t"+"---被唤醒了");},"t1").start();//暂停几秒钟线程try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); }new Thread(() -> {//     synchronized (objectLock) {objectLock.notify();System.out.println(Thread.currentThread().getName()+"\t ---发出通知");//     }},"t2").start();}
}
//t1   ---- come in
//Exception in thread "t1" java.lang.IllegalMonitorStateException
//  at java.lang.Object.wait(Native Method)
//  at java.lang.Object.wait(Object.java:502)
//  at com.zhang.admin.controller.LockSupportDemo.lambda$main$0(LockSupportDemo.java:15)
//  at java.lang.Thread.run(Thread.java:748)

说明要使用waitnotify必须加synchronized

  • 异常2—把notify和wait的执行顺序对换
public class LockSupportDemo
{public static void main(String[] args){Object objectLock = new Object();new Thread(() -> {try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }synchronized (objectLock) {System.out.println(Thread.currentThread().getName()+"\t ---- come in");try {objectLock.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+"\t"+"---被唤醒了");},"t1").start();new Thread(() -> {synchronized (objectLock) {objectLock.notify();//这个先执行了System.out.println(Thread.currentThread().getName()+"\t ---发出通知");}},"t2").start();}
}
//一直阻塞,线程不能正常结束

说明waitnotify顺序不能对换

小总结

  • wait和notify方法必须要在同步块或者方法里面,且成对出现使用
  • 先wait后notify才OK

②Condition接口中的await后signal方法实现线程的等待和唤醒

  • 正常情况:
public class LockSupportDemo
{public static void main(String[] args){Lock lock = new ReentrantLock();Condition condition = lock.newCondition();new Thread(() -> {lock.lock();try{System.out.println(Thread.currentThread().getName()+"\t-----come in");condition.await();System.out.println(Thread.currentThread().getName()+"\t -----被唤醒");} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}},"t1").start();//暂停几秒钟线程try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }new Thread(() -> {lock.lock();try{condition.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}System.out.println(Thread.currentThread().getName()+"\t"+"我要进行唤醒");},"t2").start();}
}
//t1  -----come in
//t2  我要进行唤醒
//t1   -----被唤醒
  • 异常1 原理同上
    • 仍然返回IllegalMonitorStateException
  • 异常2 原理同上
    • 仍然在不停的循环

小总结

  • awaitsignal类似于上面waitnotify
    • Condition中的线程等待和唤醒方法,需要 先获取锁
    • 一定要先await后signal,不能反了

Object和Condition使用的限制

  • 线程先要获得并持有锁,必须在锁块(synchronized或lock)中
  • 必须要先等待后唤醒,线程才能够被唤醒

③LockSupport类中的park等待和unpark唤醒

  • LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。

  • LockSupport类使用了一种名为Permit(许可) 的概念来做到阻塞和唤醒线程的功能, 每个线程都有一个许可(permit),permit(许可)只有两个值1和0,默认是0。0 是阻塞,1是唤醒

  • 可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是1。

阻塞

  • park()/park(Object blocker)

  • 调用LockSupport.park()时,发现它调用了unsafe类,并且默认传了一个0

  • permit默认是零,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park方法会被唤醒,
    然后会将permit再次设置为零并返回。

唤醒

  • 调用LockSupport.unpark();时,也调用了unsafe类

  • 调用unpark(thread)方法后,就会将thread线程的许可permit设置成1(注意多次调用unpark方法,不会累加,permit值还是1)会自动唤醒thread线程,即之前阻塞中的LockSupport.park()方法会立即返回。

举个栗子:

  • 正常情况且无锁块要求
public class LockSupportDemo
{public static void main(String[] args) {Thread t1 = new Thread(()->{System.out.println(Thread.currentThread().getName()+"\t----------come in");LockSupport.park();System.out.println(Thread.currentThread().getName()+"\t----------被唤醒了");},"t1");t1.start();new Thread(()->{LockSupport.unpark(t1);System.out.println(Thread.currentThread().getName()+"\t-----发出通知,去唤醒t1");},"t2").start();}
}
//t1  ----------come in
//t2  -----发出通知,去唤醒t1
//t1  ----------被唤醒了
  • 之前错误的 先唤醒后等待,LockSupport照样支持
public class LockSupportDemo
{public static void main(String[] args) {Thread t1 = new Thread(()->{try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"\t----------come in"+"\t"+System.currentTimeMillis());LockSupport.park();System.out.println(Thread.currentThread().getName()+"\t----------被唤醒了"+"\t"+System.currentTimeMillis());},"t1");t1.start();new Thread(()->{LockSupport.unpark(t1);System.out.println(Thread.currentThread().getName()+"\t-----发出通知,去唤醒t1");},"t2").start();}
}
//t2  -----发出通知,去唤醒t1
//t1  ----------come in  1654750785663
//t1  ----------被唤醒了  1654750785663

sleep方法3秒后醒来,执行park无效,没有阻塞效果,解释如下。先执行了unpark(t1)导致上面的park方法形同虚设无效,时间是一样的。类似于高速公路的ETC,提前买好了通行证 unpark,到闸机处直接抬起栏杆放行了,没有park拦截了

许可证是只要一个的,不可累加

小总结

  • LockSupport是用来创建锁和其他同步类基本线程阻塞原语

  • LockSupport是一个线程阻塞工具类, 所有的方法都是静态方法, 可以让线程在任意位置阻塞, 阻塞之后也有对应的唤醒方法。归根结底, LockSupport调用的Unsafe中的native代码。

  • Lock Support提供park() 和unpark() 方法实现阻塞线程和解除线程阻塞的过程

  • Lock Support和每个使用它的线程都有一个许可(permit) 关联。

  • 每个线程都有一个相关的permit, permit最多只有一个, 重复调用unpark也不会积累凭证。

也就是说:

线程阻塞需要消耗凭证permit ,这个凭证最多只有一个

当调用park方法时:

  • 如果有凭证,就会直接消耗掉这个凭证然后正常退出
  • 没有凭证,就必须阻塞等待凭证可用

而unpark 刚好相反,他会增加一个凭证,但是凭证最多只能有一个,累加无效

为什么LockSupport可以突破wait/notify的原有调用顺序?

因为unpark获得了一个凭证, 之后再调用park方法, 就可以名正言顺的凭证消费, 故不会阻塞。
先发放了凭证后续可以畅通无阻

为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?

因为凭证的数量最多为1, 连续调用两次unpark和调用一次unpark效果一样, 只会增加一个凭证;
而调用两次park却需要消费两个凭证, 证不够, 不能放行

相关内容

热门资讯

【MySQL】锁 锁 文章目录锁全局锁表级锁表锁元数据锁(MDL)意向锁AUTO-INC锁...
【内网安全】 隧道搭建穿透上线... 文章目录内网穿透-Ngrok-入门-上线1、服务端配置:2、客户端连接服务端ÿ...
GCN的几种模型复现笔记 引言 本篇笔记紧接上文,主要是上一篇看写了快2w字,再去接入代码感觉有点...
数据分页展示逻辑 import java.util.Arrays;import java.util.List;impo...
Redis为什么选择单线程?R... 目录专栏导读一、Redis版本迭代二、Redis4.0之前为什么一直采用单线程?三、R...
【已解决】ERROR: Cou... 正确指令: pip install pyyaml
关于测试,我发现了哪些新大陆 关于测试 平常也只是听说过一些关于测试的术语,但并没有使用过测试工具。偶然看到编程老师...
Lock 接口解读 前置知识点Synchronized synchronized 是 Java 中的关键字,...
Win7 专业版安装中文包、汉... 参考资料:http://www.metsky.com/archives/350.htm...
3 ROS1通讯编程提高(1) 3 ROS1通讯编程提高3.1 使用VS Code编译ROS13.1.1 VS Code的安装和配置...
大模型未来趋势 大模型是人工智能领域的重要发展趋势之一,未来有着广阔的应用前景和发展空间。以下是大模型未来的趋势和展...
python实战应用讲解-【n... 目录 如何在Python中计算残余的平方和 方法1:使用其Base公式 方法2:使用statsmod...
学习u-boot 需要了解的m... 一、常用函数 1. origin 函数 origin 函数的返回值就是变量来源。使用格式如下...
常用python爬虫库介绍与简... 通用 urllib -网络库(stdlib)。 requests -网络库。 grab – 网络库&...
药品批准文号查询|药融云-中国... 药品批文是国家食品药品监督管理局(NMPA)对药品的审评和批准的证明文件...
【2023-03-22】SRS... 【2023-03-22】SRS推流搭配FFmpeg实现目标检测 说明: 外侧测试使用SRS播放器测...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
初级算法-哈希表 主要记录算法和数据结构学习笔记,新的一年更上一层楼! 初级算法-哈希表...
进程间通信【Linux】 1. 进程间通信 1.1 什么是进程间通信 在 Linux 系统中,进程间通信...
【Docker】P3 Dock... Docker数据卷、宿主机与挂载数据卷的概念及作用挂载宿主机配置数据卷挂载操作示例一个容器挂载多个目...