——LockSupport 与 线程中断
线程中断机制
-
一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止,所以,
Thread.stop,Thread.suspend,Thread.resume
都已经被废弃 -
在 Java 中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java 提供了一种用于停止线程的协商机制——中断,即中断标识协商机制
-
中断只是一种协作协商机制,Java 没有给中断增加任何语法,中断的过程完全需要程序员自己实现。若要中断一个线程,你需要手动调用该线程的 interrupt 方法,该方法也仅仅是将线程对象的中断标识设成 true;接着你需要自己写代码不断地检测当前线程的标识位,如果为 true,表示别的线程请求这条线程中断,此时该做什么需要你自己写代码实现
-
每个线程对象中都有一个中断标识位,用于表示线程是否被中断;该标识位为 true 表示中断,为 false 表示未中断;通过调用线程对象的 interrupt 方法将该线程的标识位设为 true,可以在别的线程中调用,也可以在自己的线程中调用
-
interrupt()
:设置线程的中断状态为 true,发起一个协商而不会立刻停止线程 -
interrupted()
:- 返回当前线程的中断状态,测试当前线程是否已被中断
- 将当前线程的中断状态清零并重新设为 false,清除线程的中断状态
- 此方法不好理解,如果连续两次调用此方法,则第二次调用将返回 fasle,因为连续调用两次的结果可能不太一样
-
isInterrupted()
: 判断当前线程是否已被中断(通过检查中断标志位)
如何停止中断运行中的线程
- 通过一个 volatile 变量实现:一个线程修改变量值,另一个线程监听,当变量状态值发生变更,跳出线程运行
- 通过 AtomicBoolean 变量实现
- 通过 Thread 类自带的中断 api 实例方法实现
- 具体来说,当对一个线程调用 interrupt() 时
- 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程继续正常运行,不受影响。所以,interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行
- 如果线程处于阻塞状态(sleep,wait,join等状态),在别的线程中调用当前线程对象的 interrupt() ,那么线程将立即退出被阻塞状态,并抛出 InterruptedException 异常,中断状态将会被清除(线程无法中断,继续运行,需要在异常出再次中断)
- 具体来说,当对一个线程调用 interrupt() 时
LockSupport
- 是一个线程阻塞工具类,是用来创建锁 和 其他同步类 的基本线程阻塞原语
- LockSupport 中的
park()
和unpark(Thread thread)
的作用分别是 阻塞线程 和 解除阻塞线程(唤醒) - LockSupport 类使用了一种名为 Permit(许可证) 的概念做到 阻塞和唤醒线程 的功能,每个线程都有一个许可(permit),许可的累加上限是1
线程等待和唤醒
- 使用 Object 中的 wait 方法让线程等待,使用 Object 中的 notify 方法唤醒线程
- wait 和 notify 方法必须要在同步块或者方法里面, 且成对出现使用
- 顺序上先 wait 后 notify,否则无法唤醒
- 使用 JUC 包中的 Condition 的 await 方法让线程等待,使用 signal 方法唤醒线程
- Condition 中的线程等待和唤醒方法,需要先获取锁
- 一定要先 await 后 signal,线程才能被唤醒
以上两个对象使用的限制条件
- 线程先要获得并持有锁,必须在锁块(synchronized 或 lock)中
- 必须要先等待后唤醒,线程才能够被唤醒
LockSupport 无限制条件
- LockSupport 类可以阻塞当前线程以及唤醒指定被阻塞的线程
- 许可证不会累计,只有一个,不允许多对多
为什么可以突破 wait、notify 的原有调用顺序?
- 因为 unpark 获得了一个凭证,之后再调用 park 方法,就可以名正言顺的凭证消费,不会阻塞,先发放了凭证后续可以畅通无阻
为什么唤醒两次后阻塞两次,但最终结果还是会阻塞线程?
- 因为凭证的数量最多为1,连续调用两次 unpark 和调用一次 unpark 效果一致,只会增加一个凭证;而调用两次 park 却需要消费两个凭证,证不够,不能房型