LockSupport与线程中断
4、LockSupport与线程中断
4.1 LockSupport
4.2 线程中断机制
4.3 从阿里蚂蚁金服面试题讲起
如何中断一个运行中的线程??
如何停止一个运行中的线程??
4.4 什么是中断机制?
- 首先,一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止,自己来决定自己的命运。所以,Thread.stop,Thread.suspend,Thread.resume都已经被废弃了。
- 其次,在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java提供了一种用于停止线程的协商机制——中断,也即中断标识协商机制。
- 中断只是一种协作协商机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。
若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设成true;接着你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程请求这条线程中断,
此时究竟该做什么需要你自己写代码实现。 - 每个线程对象中都有一个中断标识位,用于表示线程是否被中断;该标识位为true表示中断,为false表示未中断;通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。
4.5 中断的相关API方法之三大方法说明
4.6 通过一个volatile变量实现
1 | public class Main { |
4.7 通过AtomicBoolean
1 | public class Main { |
4.8 通过Thread类自带的中断api实例方法实现
1 | public class Main { |
4.9 API
4.10 code
1 | public class Main { |
4.11 实例方法interrupt(),没有返回值
4.12 interrupt()源码分析
4.13 实例方法islnterrupted,返回布尔值
4.14 islnterrupted源码分析
4.15 API说明
- 具体来说,当对一个线程,调用interrupt()时:
- ①如果线程处于正常活动状态,那么会将该线程的中断标志设置为true,仅此而己。被设置中断标志的线程将继续正常运行,不受影响。所以,interrupt()并不能真正的中断线程,需要被调用的线程自己进行配合才行。
- ②如果线程处于被阻塞状态(例如处于sleep,wait,join等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。
4.16 code02
1 | public class InterruptDemo2 { |
4.17 code02后手案例(重要,面试就是它,操蛋)
1 | /** |
4.18 结论
- sleep方法抛出InterruptedException后,中断标识也被清空置为false,我们在catch没有通过调用th.interrupt()方法再次将中断标识置为true,这就导致无限循环了。
4.19 静态方法Thread.interrupted()的说明
4.20 code
1 | public class InterruptDemo4 { |
4.21 都会返回中断状态,两者对比
4.22 结论
- 方法的注释也清晰的表达了“中断状态将会根据传入的Clearlnterrupted参数值确定是否重置”。
- 所以,静态方法interrupted将会清除中断状态(传入的参数Clearlnterupted为true),实例方法islnterrupted则不会(传入的参数Clearlnterrupted为false)。
4.23 LockSupport是什么
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
下面这句话,后面详细说:LockSupport中的park()和unpark()的作用分别是阻塞线程和解除阻塞线程。
4.24 Object类中的wait和notify方法实现线程等待和唤醒之正常
1 | public class Main { |
4.25 Object类中的wait和notify方法实现线程等待和唤醒之异常1
1 | public class Main { |
4.26 Object类中的wait和notify方法实现线程等待和唤醒之异常2
1 | public class Main { |
4.27 Condition接口中的await后signal方法实现线程的等待和唤醒之正常
1 | public class Main { |
4.28 Condition接口中的await后signal方法实现线程的等待和唤醒之异常1
1 | public class Main { |
4.29 Condition接口中的await后signal方法实现线程的等待和唤醒之异常2
1 | public class Main { |
4.30 LockSupport类中的park等待和unpark唤醒官网解释
- LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
- LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit),但与Semaphore不同的是,许可的累加上限是1。
4.31 API
4.32 park()/park(Object blocker)
- permit许可证默认没有不能放行,所以一开始调park()方法当前线程就会阻塞,直到别的线程给当前线程的发放permit,park方法才会被唤醒。
4.33 unpark(Thread thread)
- 调用unpark(thread)方法后,就会将thread线程的许可证permit发放,会自动唤醒park线程,即之前阻塞中的LockSupport.park()方法会立即返回。
4.34 正常+无锁块要求
1 | public class Main { |
4.35 之前错误的先唤醒后等待,LockSupport照样支持
1 | public class Main { |
4.36 解释
- 类似高速公路的ETC,提前买好了通行证unpark,到闸机处直接抬起栏杆放行了,没有park拦截了。
4.37 重点说明(重要)
- LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
- LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。归根结底,LockSupport调用的Unsafe中的native代码。
- LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程。
- LockSupport和每个使用它的线程都有一个许可(permit)关联。每个线程都有一个相关的permit,permit最多只有一个,重复调用unpark也不会积累凭证。
- 形象的理解:线程阻塞需要消耗凭证(permit),这个凭证最多只有1个。
- 当调用park方法时:
- 如果有凭证,则会直接消耗掉这个凭证然后正常退出;
- 如果无凭证,就必须阻塞等待凭证可用;
- 而unpark则相反,它会增加一个凭证,但凭证最多只能有1个,累加无效。
4.38 面试题
为什么可以突破wait/notify的原有调用顺序?
- 因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞。
先发放了凭证后续可以畅通无阻。
- 因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞。
为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?
- 因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会增加一个凭证;
而调用两次park却需要消费两个凭证,证不够,不能放行。
- 因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会增加一个凭证;