LockSupport 是一个用来创建锁和其他同步工具类的基本线程阻塞原语
使用
说到线程阻塞和唤醒自然而然就会拿他和 Object.wait()/Object.notify()
进行对比
我们直接上代码
任何地方都可以使用
以 wait & notify 举例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public static void waitNotify() throws InterruptedException {
Object lock = new Object();
Thread waitThread = new Thread(() -> { synchronized (lock) { try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("waitThread get notified"); }, "waitThread");
waitThread.start();
Thread.sleep(500L);
Thread notifyThread = new Thread(() -> { System.out.println("notifyThread notify waitThread"); synchronized (lock) { lock.notify(); } }, "notifyThread"); notifyThread.start();
}
|
换成 LockSupport
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public static void parkUnpark() throws InterruptedException {
Thread parkThread = new Thread(() -> { System.out.println("parkThread blocked by park");
LockSupport.park();
System.out.println("parkThread notified by unparkThread"); }, "parkThread"); parkThread.start();
Thread.sleep(500L);
Thread unparkThread = new Thread(() -> {
System.out.println("unparkThread notify parkThread");
LockSupport.unpark(parkThread);
}, "unparkThread"); unparkThread.start();
}
|
可以发现 LockSupport 不像是 wait/notify 那样必须要在 synchronized 下才能使用
无唤醒顺序依赖
此外,LockSupport 相比于 wait/notify ,还解决了顺序问题,看下面这段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public static void unorderedWaitNotify() throws InterruptedException {
Object lock = new Object();
Thread waitThread = new Thread(() -> { try { Thread.sleep(2000L); } catch (InterruptedException e) { throw new RuntimeException(e); } synchronized (lock) { try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("waitThread get notified"); }, "waitThread"); waitThread.start();
Thread notifyThread = new Thread(() -> { System.out.println("notifyThread notify waitThread"); synchronized (lock) { lock.notify(); } }, "notifyThread"); notifyThread.start();
}
|
上面这段代码,我们模拟先 notify 再 wait,结果就是 waitThread 永远地阻塞住了,程序无法正常结束
但是如果换用 LockSupport
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public static void unorderedParkUnpark() throws InterruptedException {
Thread parkThread = new Thread(() -> { try { Thread.sleep(2000L); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("parkThread blocked by park");
LockSupport.park();
System.out.println("parkThread notified by unparkThread"); }, "parkThread"); parkThread.start();
Thread unparkThread = new Thread(() -> {
System.out.println("unparkThread notify parkThread");
LockSupport.unpark(parkThread);
}, "unparkThread"); unparkThread.start();
}
|
我们先 unpark 再 park,结果可以发现虽然 unparkThread 先执行了 unpark 尝试唤醒,但是此时 parkThread 还在睡,还没 park 操作。但是不影响,2s 后 parkThread 调用 park 后立刻就被唤醒了
这也是 park/unpark
和 wait/notify
的另一个核心区别:不论调用顺序,只要成对出现,必然可以唤醒线程
中断状态处理
在使用层面,wait()
会抛出 InterruptedException 需要手动 catch 处理,但是 park()
不会,park()
确实会响应线程中断,但是只是不抛出异常。除此之外 park()
中断后不会 clear 中断状态,而 wait()
中断后抛出异常会 clear 中断状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public static void waitInterrupted() {
Object lock = new Object();
Thread waitThread = new Thread(() -> { synchronized (lock) { try { lock.wait(); } catch (InterruptedException e) { System.out.println("park thread interrupted, interrupted: " + Thread.currentThread().isInterrupted()); } } }, "waitThread");
waitThread.start(); waitThread.interrupt(); }
|
1 2 3 4 5 6 7 8 9 10 11 12
| public static void parkInterrupted() { Thread parkThread = new Thread(() -> {
LockSupport.park(); System.out.println("park thread interrupted, interrupted: " + Thread.currentThread().isInterrupted());
}, "parkThread");
parkThread.start(); parkThread.interrupt(); }
|
为什么不会清除中断状态
那么为什么 wait() 中断后会清除状态而 park() 中断后不会清除状态呢?
原因需要从 wait() 以及 park() 使用和设计的角度出发:
- wait() 面向程序业务编写者,抛出的 InterruptedException 就是在强制要求处理中断后的逻辑,清除状态是为了后续业务逻辑能正常运行
- park() 面向上层工具(例如 ReentrantLock),保留中断状态是为了供上层工具调用
Thread.currentThread().interrupted()
查询中断状态后进行定制化的进一步处理(例如告警、忽略、抛出异常)
还是看代码
对于 wait,就像是餐厅等餐
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public class WaitScenario { static Object foodCounter = new Object(); static boolean foodReady = false;
public static void main(String[] args) throws InterruptedException { Thread customer = new Thread(() -> { synchronized (foodCounter) { try { while (!foodReady) { System.out.println("顾客:餐还没好,我先等会儿..."); foodCounter.wait(); } System.out.println("顾客:拿到餐了,走人~"); } catch (InterruptedException e) { System.out.println("顾客:接到紧急电话,不等了!"); System.out.println("中断状态(清除后):" + Thread.currentThread().isInterrupted()); } } }, "顾客");
customer.start(); Thread.sleep(500); customer.interrupt(); } }
|
在这个场景中,wait()
的核心是 “等待某个条件(餐做好)”,中断的含义是 “外部要求停止等待”
在中断后如果不清除中断状态,假设顾客中断后又想 “重新等待”(比如电话是误报)(其实就对应线程后续可能存在的业务逻辑),后续的wait()
会因为 “中断状态残留” 直接失败(因为wait()
会检查中断状态,有残留就直接抛异常)
而对于 park,更像是用在客制化锁的锁芯设计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| import java.util.concurrent.locks.LockSupport;
public class ParkScenario { static class DoorLock { Thread owner;
public void lock() { while (!Thread.currentThread().equals(owner)) { System.out.println(Thread.currentThread().getName() + ":在门外等..."); LockSupport.park();
if (Thread.currentThread().isInterrupted()) { System.out.println("门禁系统:检测到撬锁!"); Thread.interrupted(); } } }
public void unlock() { owner = null; } }
public static void main(String[] args) throws InterruptedException { DoorLock door = new DoorLock();
Thread personA = new Thread(() -> { door.lock(); System.out.println("路人甲:进门了~"); }, "路人甲");
personA.start(); Thread.sleep(500); personA.interrupt(); } }
|
在这个场景中,park()
是底层工具,用来实现 “阻塞等待”,而上层的DoorLock
需要自主决定如何处理异常情况(比如撬锁)
如果park()
清除了中断状态,DoorLock
的lock()
方法就无法知道 “线程被中断过”(即 “被撬过”),也就无法执行报警、日志等逻辑 —— 灵活性完全丧失
保留状态后,上层工具(DoorLock
)可以通过isInterrupted()
查询到 “被中断过”,再根据需求自定义处理(比如抛异常、重试、忽略),这正是park()
作为 “底层原语” 的设计目的
简单说:wait()
是给 “直接用它写业务” 的人用的,必须强制处理中断并清状态,避免业务混乱;park()
是给 “造工具” 的人用的,保留状态才能让工具更灵活
总结
通过 LockSupport 的 park/unpark
和 wait/notify
的对比,我们可以简单总结:他们都可以实现线程的等待和唤醒。但是区别在于
特性 |
wait()/notify() |
LockSupport.park()/unpark() |
同步要求 |
必须在synchronized 块中调用 |
无任何同步要求,可在任意位置调用 |
唤醒顺序依赖 |
必须先wait 后notify ,否则失效 |
顺序无关,unpark 可提前 “预支” 许可 |
唤醒目标 |
notify() 随机唤醒一个等待线程 |
unpark(thread) 精确唤醒指定线程 |
中断响应 |
抛出InterruptedException ,清除中断状态 |
不抛异常,保留中断状态 |
底层机制 |
依赖对象监视器(monitor) |
依赖 “许可”(permit)机制 |
而对于中断状态的处理,二者的处理逻辑不同
场景 |
wait() 的逻辑(餐厅取餐) |
park() 的逻辑(门禁锁开发) |
使用者 |
业务开发者(直接处理等待 / 唤醒) |
工具类开发者(用它构建更复杂的锁 / 同步工具) |
中断的含义 |
“停止等待”(必须显式处理,否则业务会出错) |
“异常情况”(需要留给工具类自主决定如何处理) |
状态处理的必要性 |
清除状态:避免影响后续的等待逻辑(比如重新等待) |
保留状态:让上层工具能感知异常,实现灵活处理(报警 / 忽略等) |