第四章 LockSupport与线程中断
4.1 线程中断机制
4.1.1 从阿里蚂蚁金服面试题讲起
java.lang.Thread 下的三个方法:

-
如何中断一个运行中的线程?
-
如何停止一个运行中的线程?
4.1.2 什么是中断机制
首先,一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止,自己来决定自己的命运,所以Thread.stop,Thread.suspend,Thread.resume 都已经被废弃了
其次,在 Java 中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java 提供了一种用于停止线程的协商机制----中断,也即中断标识协商机制
-
中断只是一种协作协商机制,Java 没有给中断增加任何语法,中断的过程完全需要程序员自行实现。若要中断一个线程,你需要手动调用该线程 interrupt 方法,该方法也仅仅是将该线程对象的中断标识设置为 true,接着你需要自己写代码不断检测当前线程的标识位,如果为 true,表示别的线程请求这条线程中断,此时究竟应该做什么需要你自己写代码实现
-
每个线程对象都有一个中断标识位,用于表示线程是否被中断;该标识位为 true 表示中断,为 false 表示未中断;通过调用线程对象的 interrupt 方法将该线程的标识位设置为 true;可以在别的线程中调用,也可以在自己的线程中调用
4.1.3 中断的相关API方法之三大方法说明
public void interrupt()
-
实例方法 Just to set the interrupt flag
-
实例方法仅仅是设置线程的中断状态为 true,发起一个协商而不会立刻停止线程
public static boolean interrupted()
-
静态方法 Thread.interrupted()
-
判断线程是否被中断并清除当前中断状态(做了两件事情)
-
返回当前线程的中断状态,测试当前线程是否已被中断
-
将当前线程的中断状态清零并重新设置为 false,清除线程的中断状态
-
这个方法有点不好理解在于如果连续两次调用此方法,则第二次返回 false,因为连续调用两次的结果可能不一样
-
public boolean isInterrupted()
-
实例方法
-
判断当前线程是否被中断(通过检查中断标志位)
4.1.4 大厂面试题中断机制考点
如何停止中断运行中的线程?
-
通过一个 volatile 变量实现
public class InterruptDemo { private static volatile boolean isStop = false; /** * 运行结果: * Hello from a thread! * Hello from a thread! * ... * Hello from a thread! * Thread is stopped. */ public static void main(String[] args) { new Thread(() -> { while (true) { if (isStop) { System.out.println("Thread is stopped."); break; } System.out.println("Hello from a thread!"); } }).start(); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { isStop = true; }).start(); } } -
通过 AutomicBoolean
public class InterruptDemo { private static AtomicBoolean atomicBoolean = new AtomicBoolean(false); /** * 运行结果: * Hello from a thread! * Hello from a thread! * ... * Hello from a thread! * Thread is stopped. */ public static void main(String[] args) { new Thread(() -> { while (true) { if (atomicBoolean.get()) { System.out.println("Thread is stopped."); break; } System.out.println("Hello from a thread!"); } }).start(); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { atomicBoolean.set(true); }).start(); } } -
通过 Thread 类自带的中断 API 实例方法实现----在需要中断的线程中不断监听中断状态,一旦发生中断,就执行相应的中断处理业务逻辑 stop 线程
public class InterruptDemo { /** * 运行结果: * Hello from a thread! * Hello from a thread! * ... * Hello from a thread! * Thread is stopped. */ public static void main(String[] args) { Thread t1 = new Thread(() -> { while (true) { if (Thread.currentThread().isInterrupted()) { System.out.println("Thread is stopped."); break; } System.out.println("Hello from a thread!"); } }); t1.start(); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { t1.interrupt(); }).start(); // t1.interrupt(); } }
当前线程的中断标识为 true,是不是线程立刻停止?
答案是不立刻停止,具体来说,当对一个线程,调用 interrupt 时
-
如果线程处于正常活动状态,那么会将该线程的中断标识设置为 true,仅此而已,被设置中断标志的线程将继续正常运行,不受影响,所以 interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行,对于不活动的线程没有任何影响
public class InterruptDemo2 { /** * 执行 interrupt 方法将 t1 标志位设置为 true 后,t1 没有中断,仍然完成了任务后再结束 * 在 2000 毫秒后,t1 已经结束称为不活动线程,设置状态为没有任何影响 * * 运行结果: * t1线程默认的中断标识:false * ----------:1 * ----------:2 * ... * ----------:300 * t1线程调用 interrupt() 后的中断标识02:false * t1线程调用 interrupt() 后的中断标识01:false * t1线程调用 interrupt() 后的中断标识03:false */ public static void main(String[] args) { //实例方法 interrupt() 仅仅是设置线程的中断状态位为true,不会停止线程 Thread t1 = new Thread(() -> { for (int i = 1; i <= 300; i++) { System.out.println("----------:" + i); } System.out.println("t1线程调用 interrupt() 后的中断标识02:" + Thread.currentThread().isInterrupted()); }, "t1"); t1.start(); System.out.println("t1线程默认的中断标识:" + t1.isInterrupted()); try { TimeUnit.MILLISECONDS.sleep(2); } catch (InterruptedException e){ e.printStackTrace(); } t1.interrupt();//true System.out.println("t1线程调用 interrupt() 后的中断标识01:" + t1.isInterrupted()); try { TimeUnit.MILLISECONDS.sleep(2000); } catch (InterruptedException e){ e.printStackTrace(); } System.out.println("t1线程调用 interrupt() 后的中断标识03:" + t1.isInterrupted()); } } -
如果线程处于阻塞状态(例如 sleep,wait,join 状态等),在别的线程中调用当前线程对象的 interrupt 方法,那么线程将立刻退出阻塞状态(interrupt 状态也将被清除),并抛出一个 InterruptedException 异常
public class InterruptDemo3 { /** * 1. 中断标志位默认为false * 2. t2 对 t1 发出中断协商 t1.interrupt(); * 3. 中断标志位为true: 正常情况 程序停止 * 中断标志位为true 异常情况, InterruptedException ,将会把中断状态清楚,中断标志位为 false * 4. 需要在 catch 块中,再次调用 interrupt() 方法将中断标志位设置为 false; * * 运行结果: * ----- hello InterruptDemo3 * ----- hello InterruptDemo3 * Thread-0中断标志位:true 程序停止 * java.lang.InterruptedException: sleep interrupted * at java.lang.Thread.sleep(Native Method) * at com.my.demo.InterruptDemo3.lambda$main$0(InterruptDemo3.java:23) * at java.lang.Thread.run(Thread.java:748) */ public static void main(String[] args) { Thread t1 = new Thread(() -> { while (true) { if (Thread.currentThread().isInterrupted()) { System.out.println(Thread.currentThread().getName() + "中断标志位:" + Thread.currentThread().isInterrupted() + " 程序停止"); break; } try { Thread.sleep(1000); } catch (InterruptedException e) { //为什么要在异常出,再调用一次 Thread.currentThread().interrupt(); e.printStackTrace(); } System.out.println("----- hello InterruptDemo3"); } }); t1.start(); //暂停几秒 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { t1.interrupt(); }, "t2").start(); } }对于当前情况的源码分析如下:
![image-1776034884109]()
总之,需要记住的是中断只是一种协商机制,修改中断标识位仅此而已,不是立刻 stop 打断
静态方法 Thread.interrupted(),谈谈你的理解?

public class InterruptDemo4 {
/**
* 测试当前线程是否被中断(检查中断标识),返回一个 boolean 并清除中断状态,
* 第二次再调用时中断状态已经被清除,将返回一个 false
*
* 运行结果:
* main false
* main false
* ----1
* main true
* main false
*/
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();
System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());
System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());
}
}
对于静态方法 Thread.interrupted() 和实例方法 isInterrupted()的区别在于:
-
静态方法
interrupted将会清除中断状态(传入的参数 ClearInterrupted 为 true) -
实例方法
isInterrupted则不会(传入的参数 ClearInterrupted 为 false)

4.1.5 总结
-
public void interrupt()是一个实例方法,它通知目标线程中断,也仅仅是设置目标线程的中断标识位为 true -
public boolean isInterrupted()是一个实例方法,它判断当前线程是否被中断(通过检查中断标志位)并获取中断标志 -
public static boolean interrupted()是一个静态方法,返回当前线程的中断真实状态(boolean 类型),后会将当前线程的中断状态设置为 false,此方法调用之后会清除当前线程的中断标志位(将中断标志置为 false),返回当前值并清零置为 false
4.2 LockSupport 是什么
LockSupport 是用来创建锁和其他同步类的基本线程阻塞原语,其中park()和unpack()而作用分别是阻塞线程和解除阻塞线程
4.3 线程等待唤醒机制
4.3.1 三种让线程等待和唤醒的方法
-
使用 Object 中的
wait()方法让线程等待,使用 Object 中的notify()方法唤醒线程 -
使用 JUC 包中的 Condition 的
await()方法让线程等待,使用signal()方法唤醒线程 -
LockSupport 类可以阻塞当前线程以及唤醒指定被阻塞的线程
4.3.2 Object 类中的 wait 和 notify 方法实现线程等待和唤醒
-
wait 和 notify 方法必须要在同步代码块或者方法里面,且成对出现使用
-
先 wait 再 notify 才 ok
public class LockSupportDemo {
/**
* 运行结果:
* t1 ----------com in
* t2 ----------发出通知
* t1 ----------被唤醒
*/
public static void main(String[] args) {
Object objectLock = new Object();
new Thread(() -> {
synchronized (objectLock) {
System.out.println(Thread.currentThread().getName() + "\t ----------com in");
try {
objectLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t ----------被唤醒");
}
}, "t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
synchronized (objectLock) {
objectLock.notify();
System.out.println(Thread.currentThread().getName() + "\t ----------发出通知");
}
}, "t2").start();
}
}
4.3.3 Condition 接口中的 await 和 signal 方法实现线程的等待和唤醒
-
Condition 中的线程等待和唤醒方法,需要先获取锁
-
一定要先 await 后 signal,不要反了
public class LockSupportDemo {
/**
* 运行结果:
* t1 ----------com in
* t2 ----------发出通知
* t1 ----------被唤醒
*/
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 ----------com 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();
System.out.println(Thread.currentThread().getName() + "\t ----------发出通知");
} finally {
lock.unlock();
}
}, "t2").start();
}
}
4.3.4 上述两个对象 Object 和 Condition 使用的限制条件
-
线程需要先获得并持有锁,必须要在锁块(synchronized 或 lock)中
-
必须要先等待后唤醒,线程才能被唤醒
4.3.5 LockSupport 类中的 park 等待和 unpark 唤醒
是什么?
-
LockSupport 是用于创建锁和其他同步类的基本线程阻塞原语
-
LockSupport 类使用了一种名为 Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(Permit),许可证只能有一个,累加上线是 1
主要方法
-
阻塞:Permit 许可证默认没有不能放行,所以一开始调用 park() 方法当前线程会阻塞,直到别的线程给当前线程发放 Permit,park 方法才会被唤醒
park/park(Object blocker)----- 阻塞当前线程/阻塞传入的具体线程 -
唤醒:调用
unpack(thread)方法后,就会将 thread 线程的许可证 Permit 发放,会自动唤醒 park 线程,即之前阻塞中的LockSupport.park()方法会立刻返回unpark(Thread thread)----- 唤醒处于阻塞状态的指定线程
public class LockSupportDemo {
/**
* 运行结果:
* t1 ----------com in
* t2 ----------发出通知
* t1 ----------被唤醒
*/
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t ----------com in");
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "\t ----------被唤醒");
}, "t1");
t1.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
LockSupport.unpark(t1);
System.out.println(Thread.currentThread().getName() + "\t ----------发出通知");
}, "t2").start();
}
}
重点说明(重要)
-
LockSupport 是用来创建锁和其他同步类的基本线程阻塞原语,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞后也有对应的唤醒方法。归根结底,LockSupport 是调用 Unsafe 中的 native 代码
-
LockSupport 提供
park()和unpark()方法实现阻塞线程和解除线程阻塞的过程,LockSupport 和每个使用它的线程都有一个许可(Permit)关联,每一个线程都有一个相关的 Permit,Permit 最多只有一个,重复调用 unpark 也不会积累凭证 -
形象理解:线程阻塞需要消耗凭证(Permit),这个凭证最多只有一个
-
当调用 park 时,如果有凭证,则会直接消耗掉这个凭证然后正常退出。如果没有凭证,则必须阻塞等待凭证可用
-
当调用 unpark 时,它会增加一个凭证,但凭证最多只能有 1 个,累加无效
-
面试题
为什么 LockSupport 可以突破 wait/notify 的原有调用顺序?
- 因为 unpark 获得了一个凭证,之后再调用 park 方法,就可以名正言顺的凭证消费,故不会阻塞,先发放了凭证后续可以通畅无阻
为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?
- 因为凭证的数量最多为 1,连续调用两次 unpark 和调用 unpark 效果一样,只会增加一个凭证,而调用两次 park 却需要消费两个凭证,证不够,不能放行


浙公网安备 33010602011771号