线程中断与LockSupport
线程中断与LockSupport
Thread类下的方法
问题
-
三个方法了解过吗?用在哪?
-
如何停止一个运行中的线程?
-
如何中断一个运行中的线程?
中断机制
一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。所以,Thread.stop
, Thread.suspend
, Thread.resume
都已经被废弃
了。
-
在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java提供了一种用于停止线程的协商机制——中断。
-
中断只是一种协作协商机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。
若要中断一个线程,你需要手动调用该线程的interrupt
方法,该方法也仅仅是将线程对象的中断标识设成true
;接着你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程要求这条线程中断,此时究竟该做什么需要你自己写代码实现。 -
每个线程对象中都有一个标识,用于表示线程是否被中断;该标识位为true表示中断,为false表示未中断;通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。
中断的相关API | 方法说明 |
---|---|
public void interrupt() | 实例方法,实例方法interrupt()仅仅是设置线程的中断状态为true,发起一个协商而不会立刻停止线程 |
public static boolean interrupted() | 静态方法,Thread.interrupted();判断线程是否被中断,并清除当前中断状态这个方法做了两件事:1 返回当前线程的中断状态2 将当前线程的中断状态设为false(这个方法有点不好理解,因为连续调用两次的结果可能不一样。) |
public boolean isInterrupted() | 实例方法,判断当前线程是否被中断(通过检查中断标志位) |
如何停止中断运行中的线程?
1. 通过一个volatile
变量实现
public class InterruptDemo {
static volatile boolean isStop = false;
public static void main(String[] args) {
new Thread(() -> {
while (true) {
if (isStop) {
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();
}
}
2. 通过AtomicBoolean
(原子布尔型)
public class InterruptDemo {
static AtomicBoolean atomicBoolean = new AtomicBoolean(false);
public static void main(String[] args) {
new Thread(() -> {
while (true) {
if (atomicBoolean.get()) {
System.out.println(Thread.currentThread().getName() + "\t atomicBoolean被修改为true,程序终止");
break;
}
System.out.println("t1 ------hello atomicBoolean");
}
}, "t1").start();
try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}
new Thread(()->{
atomicBoolean.set(true);
},"t2").start();
}
}
3. 通过Thread类自带的中断api方法实现
public class InterruptDemo {
public static void main(String[] args) {
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 api");
}
}, "t1");
t1.start();
try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}
new Thread(()->{
t1.interrupt();
},"t2").start();
}
}
4. interrupt和interrupted源码分析
实例方法 interrupt()
,可以看到在内部去调用了一个interrupt0()
方法,这个方法是用native修饰的说明了是调用了C底层
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // 只是为了设置中断标志
b.interrupt(this);
return;
}
}
interrupt0();
}
/* Some private helper methods */
private native void setPriority0(int newPriority);
private native void stop0(Object o);
private native void suspend0();
private native void resume0();
private native void interrupt0();
private native void setNativeName(String name);
实例方法isInterrupted()
,没有返回值
public boolean isInterrupted() {
return isInterrupted(false);
}
// 测试某些线程是否已中断。中断状态是否重置取决于传递的 ClearInterrupted 的值。
private native boolean isInterrupted(boolean ClearInterrupted);
具体说明
- 当对一个线程,调用
interrupt()
时:-
果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。所以, interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。
-
如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态(中断状态将被清除),并抛出一个InterruptedException异常。
-
当前线程的中断标识为true,是不是线程就立刻停止?
不是,中断标识只是一个标识,不会影响线程
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("t1线程调用interrupt()后的的中断标识02 \t"+Thread.currentThread().isInterrupted());
},"t1");
t1.start();
System.out.println("t1线程默认的中断标识 \t"+t1.isInterrupted());
t1.interrupt();
try {TimeUnit.MILLISECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}
System.out.println("t1线程调用interrupt()后的的中断标识01 \t"+t1.isInterrupted());
try {TimeUnit.MILLISECONDS.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}
System.out.println("t1线程调用interrupt()后的的中断标识03 \t"+t1.isInterrupted());
}
}
阻塞案例
上方说明中提到当对一个线程,调用 interrupt()
时如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态) 在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态(中断状态将被清除),并抛出一个InterruptedException异常。如果不调用就会出现死循环,下方案例演示
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) {
Thread.currentThread().interrupt(); // 加上这句话会抛异常停止程序,如果不加就一直死循环
e.printStackTrace();
}
System.out.println("hello");
}
}, "t1");
t1.start();
try {TimeUnit.MILLISECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}
new Thread(() -> t1.interrupt(), "t2").start();
}
}
阻塞案例-执行步骤
/**
* 1. 中断标志位 默认是false
* 2. t2 -----> t1发出了中断协商,t2调用 t1.interrupt(),中断标志位true
* 3. 中断标志位true,正常情况下,程序停止
* 4. 中断标志位true,异常情况下,InterruptedException,将会把中断状态清除,并且将收到InterruptedException。中断标志位false导致无限循环。
* 5. 在catch块中,需要再次给中断标志位设置为true,2次调用停止程序才ok
*
* sleep方法抛出InterruptedException后,中断标识也被清空置为false,我们在catch没有通过th.interrupt()方法再次将中断标志设置为true,这就导致无限循环了
*/
总结
中断只是一种协同机制,修改中断标识位仅此而已,而不是立刻stop打断
静态方法 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(); //中断标志位设置为true
System.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
源码
// 静态方法
Thread.interrupted();
// 源码
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
// 实例方法
Thread.currentThread().isInterrupted();
// 源码
public boolean isInterrupted() {
return isInterrupted(false);
}
总结
- 底层都调用了native方法
isInterrupted
- 底层调用方法
ClearInterrupted
值不同,中断状态将会根据传入的Clearlnterrupted
参数值确定是否重置- 静态方法
interrupted
将会清除中断状态 (传入的参数Clearlnterrupted为true) - 实例方法
isInterrupted
不会清除 (传入的参数Clearlnterrupted为false)
- 静态方法
LockSupport
用于创建锁和其他同步类的基本线程阻塞原语
核心方法:park
、unpark
线程等待唤醒机制
三种让线程等待和唤醒的方法
-
使用object中的
wait()
方法让线程等待,使用object中的notify()
方法唤醒线程 -
使用JUC包中Condition的
await()
方法让线程等待,使用signal()
方法唤醒线程 -
LockSupport
类可以阻塞当前线程以及唤醒指定被阻塞的线程
1. 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(1L); } 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 --- 被唤醒了
进程已结束,退出代码0
- 异常一
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(1L); } 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.zjh.java.LockSupportDemo.lambda$main$0(LockSupportDemo.java:18)
at java.lang.Thread.run(Thread.java:748)
Exception in thread "t2" java.lang.IllegalMonitorStateException
at java.lang.Object.notify(Native Method)
at com.zjh.java.LockSupportDemo.lambda$main$1(LockSupportDemo.java:31)
at java.lang.Thread.run(Thread.java:748)
进程已结束,退出代码0
- 异常二
notify 不能放在 wait 前面否则程序无法运行一直等待
public class LockSupportDemo {
public static void main(String[] args) {
Object objectLock = new Object();
new Thread(() -> {
// 暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(1L); } 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();
}
}
// 结果
t2 --- 发出通知
t1 --- come in
总结
-
wait和notify方法必须要在同步块或者方法里面,且成对出现使用
-
先wait后notify才OK,顺序
2. 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();
}
}
异常情况
同上方 wait 和 notify 一样
object 和 condition 方法使用基本一样
* 都需要先获取锁才可使用
* 都需要先等待在唤醒
3. LockSupport类中的park等待和unpark唤醒
概述
-
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
-
LockSupport类使用了一种名为Permit(许可) 的概念来做到阻塞和唤醒线程的功能, 每个线程都有一个许可(permit),
-
permit(许可)只有两个值1和0,默认是0。0 是阻塞,1是唤醒
-
可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是1
主要方法
源码
park()
:permit默认是零,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park方法会被唤醒,然后会将permit再次设置为零并返回
public static void park() {
UNSAFE.park(false, 0L);
}
unpark()
:调用unpark(thread)方法后,就会将thread线程的许可permit设置成1 (注意多次调用unpark方法,不会累加,permit值还是1) 会自动唤醒thread线程,即之前阻塞中的LockSupport.park()方法会立即返回。
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
代码
可以看到下方代码是无锁的,所以避免了上面必须在锁中才能执行的问题
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---发出通知");
},"t2").start();
}
}
LockSupport
支持先唤醒在阻塞,类似于高速公路的ETC,提前买好了通行证 unpark,到闸机处直接抬起栏杆放行了,没有park拦截了
public class LockSupportDemo {
public static void main(String[] args) {
Thread t1 = new Thread(()->{
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
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---发出通知");
},"t2").start();
}
}
注意:许可的上限是1,所以不要对某个线程park() 两次
总结
Lock Support
是用来创建锁和其他同步类的基本线程阻塞原语。
Lock Support
是一个线程阻塞工具类, 所有的方法都是静态方法, 可以让线程在任意位置阻塞, 阻塞之后也有对应的唤醒方法。归根结
底,Lock Support
调用的Unsafe
中的native
代码。
Lock Support
提供park()
和unpark()
方法实现阻塞线程和解除线程阻塞的过程
Lock Support
和每个使用它的线程都有一个许可(permit
) 关联。
每个线程都有一个相关的permit
,permit
最多只有一个,重复调用unpark
也不会积累凭证。
形象的理解
线程阻塞需要消耗凭证(permit
),这个凭证最多只有1个。
当调用park
时
- 如果有凭证,则会直接消耗掉这个凭证然后正常退出;
- 如果无凭证,就必须阻塞等待凭证可用;
而unpark
则相反, 它会增加一个凭证, 但凭证最多只能有1个, 累加无效。
面试题
为什么可以突破 wait/notify 的原有调用顺序?
答:因为unpark
获得了一个凭证, 之后再调用park
方法, 就可以名正言顺的凭证消费, 故不会阻塞。先发放了凭证后续可以畅通无阻。
为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?
答:因为凭证的数量最多为1
, 连续调用两次unpark
和调用一次unpark
效果一样, 只会增加一个凭证;而调用两次park
却需要消费两个凭证,证不够,不能放行。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)