thread 线程调用
LockSupport.park()
使 thread 阻塞,当 mian 线程睡眠 3 秒结束后通过
LockSupport.unpark(thread)
方法唤醒 thread 线程,thread 线程被唤醒后会执行后续的操作。另外,
LockSupport.unpark(thread)
可以指定线程对象唤醒指定的线程
。
运行结果:
Thread is parked now
Thread is unparked now
设计思路
LockSupport 的设计思路是通过许可证来实现的,就像汽车上高速公路,入口处要获取通行卡,出口处要交出通行卡,如果没有通行卡你就无法出站,当然你可以选择补一张通行卡。
LockSupport 会为使用它的线程关联一个许可证(permit)状态,permit 的语义「是否拥有许可」,0 代表否,1 代表是,默认是 0。
-
LockSupport.unpark
:指定线程关联的 permit 直接更新为 1,如果更新前的
permit<1
,唤醒指定线程
-
LockSupport.park
:当前线程关联的 permit 如果>0,直接把 permit 更新为 0,否则阻塞当前线程
来看时间线:
-
线程 A 执行
LockSupport.park
,发现 permit 为 0,未持有许可证,阻塞线程 A
-
线程 B 执行
LockSupport.unpark
(入参线程 A),为 A 线程设置许可证,permit 更新为 1,唤醒线程 A
-
线程 B 流程结束
-
线程 A 被唤醒,发现 permit 为 1,消费许可证,permit 更新为 0
-
线程 A 执行临界区
-
线程 A 流程结束
经过上面的分析得出结论 unpark 的语义明确为「使线程持有许可证」,park 的语义明确为「消费线程持有的许可」,所以 unpark 与 park 的执行顺序没有强制要求,只要控制好使用的线程即可,
unpark=>park
执行流程如下
-
permit 默认是 0,线程 A 执行 LockSupport.unpark,permit 更新为 1,线程 A 持有许可证
-
线程 A 执行 LockSupport.park,此时 permit 是 1,消费许可证,permit 更新为 0
-
执行临界区
-
流程结束
最后再补充下 park 的注意点,因 park 阻塞的线程不仅仅会被 unpark 唤醒,还可能会被线程中断(
Thread.interrupt
)唤醒,而且不会抛出 InterruptedException 异常,所以建议在 park 后自行判断线程中断状态,来做对应的业务处理。
为什么推荐使用 LockSupport 来做线程的阻塞与唤醒(线程间协同工作),因为它具备如下优点:
-
以线程为操作对象更符合阻塞线程的直观语义
-
操作更精准,可以准确地唤醒某一个线程(notify 随机唤醒一个线程,notifyAll 唤醒所有等待的线程)
-
无需竞争锁对象(以线程作为操作对象),不会因竞争锁对象产生死锁问题
-
unpark 与 park 没有严格的执行顺序,不会因执行顺序引起死锁问题,比如「Thread.suspend 和 Thread.resume」没按照严格顺序执行,就会产生死锁
面试题
阿里面试官:有 3 个独立的线程,一个只会输出 A,一个只会输出 B,一个只会输出 C,在三个线程启动的情况下,请用合理的方式让他们按顺序打印 ABCABC。
public class ABCPrinter {
private static Thread t1, t2, t3;
public static void main(String[] args) {
t1 = new Thread(() -> {
for (int i = 0; i < 2; i++) {
LockSupport.park();
System.out.print("A");
LockSupport.unpark(t2);
});
t2 = new Thread(() -> {
for (int i = 0; i < 2; i++) {
LockSupport.park();
System.out.print("B");
LockSupport.unpark(t3);
});
t3 = new Thread(() -> {
for (int i = 0; i < 2; i++) {
LockSupport.park();
System.out.print("C");
LockSupport.unpark(t1);
});
t1.start();
t2.start();
t3.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
LockSupport.unpark(t1);