LockSupport 原理与 park/unpark
问题
LockSupport.park()/unpark() 和 Object.wait()/notify() 有什么区别?为什么说 LockSupport 是更底层的线程阻塞工具?在实际生产代码中,我们什么时候该用 LockSupport 而不是传统的 wait/notify?
分析
从 wait/notify 的痛点说起
Object.wait() 和 Object.notify() 是 Java 最古老的线程协调方式,但它们有几个根深蒂固的缺陷:
- 必须在同步块内调用:
wait()/notify()必须在synchronized代码块中调用,否则运行时抛IllegalMonitorStateException。这意味着调用方必须持有锁,而锁的获取本身就有竞争。 - 先 notify 后 wait 永远丢失:如果线程 A 先调用了
notify(),线程 B 随后才执行wait(),那么 B 会永远等下去。这个时序问题在复杂并发场景下极难调试。 - 无法精确唤醒:
notify()随机唤醒一个等待线程,notifyAll()唤醒所有,都没有办法精确唤醒指定线程。 - 虚假唤醒(Spurious Wakeup):
wait()在未收到 notify 的情况下也可能返回,所以官方文档要求wait()必须永远在循环中调用。
LockSupport 的出现就是为了解决这些问题。它是 JUC(java.util.concurrent)包的基石——AQS、ReentrantLock、ForkJoinPool 等核心组件都依赖于它。
LockSupport 的核心机制
LockSupport 每个线程关联一个许可(permit),本质上是一个二元信号量(0 或 1):
unpark(Thread t):设置线程 t 的 permit 为 1(如果已经是 1,则保持不变)park():消费 permit,如果 permit 为 1 则直接返回并将 permit 置为 0;如果 permit 为 0 则阻塞线程
这个 permit 机制解决了 wait/notify 的时序问题:先调用 unpark() 再调用 park() 不会阻塞,因为 permit 已经被提前置为 1,park() 直接消费并返回。
注意:permit 不会被累计。连续调用两次
unpark()再调用一次park(),permit 仍是 1。第二次unpark()是空操作,不会把 permit 累加到 2。
底层实现
LockSupport.park() 和 unpark() 最终调用 Unsafe.park() 和 Unsafe.unpark() 这两个 native 方法。
Unsafe 层
sun.misc.Unsafe 中:
// 阻塞当前线程,isAbsolute=false 时 time 是纳秒超时
// isAbsolute=true 时 time 是绝对时间(毫秒)
public native void park(boolean isAbsolute, long time);
public native void unpark(Object thread);JVM 层(HotSpot)
在 HotSpot 的 unsafe.cpp 中,Unsafe_Park 和 Unsafe_Unpark 调用 PlatformEvent 的 park() 和 unpark()。
Linux 上(pthread 实现):
// hotspot/src/os/linux/vm/os_linux.cpp
void PlatformEvent::park() {
int status = pthread_mutex_lock(_mutex);
int counter = _counter; // 相当于 permit
_counter = 0;
status = pthread_mutex_unlock(_mutex);
if (counter > 0) return; // permit 已存在,直接返回
// 使用 pthread_cond_wait 阻塞
status = pthread_mutex_lock(_mutex);
while (_counter <= 0) {
status = pthread_cond_wait(_cond, _mutex);
}
_counter = 0;
status = pthread_mutex_unlock(_mutex);
}
void PlatformEvent::unpark() {
int status = pthread_mutex_lock(_mutex);
int s = _counter;
_counter = 1;
status = pthread_mutex_unlock(_mutex);
if (s < 1) {
// 确保唤醒等待的线程
status = pthread_cond_signal(_cond);
}
}这个实现的关键细节:
_counter就是 permit,park()先检查_counter,大于 0 就消耗掉直接返回pthread_cond_wait可能被虚假唤醒(spurious wakeup),所以外层有个while循环unpark()先设_counter = 1,再发pthread_cond_signal,保证信号不会丢失
与 wait/notify 的底层对比
| 特性 | LockSupport | Object.wait/notify |
|---|---|---|
| 底层原语 | PlatformEvent (pthread_cond + mutex) | 对象监视器 (ObjectMonitor) |
| 需要持有对象锁 | 否 | 是 |
| 存储介质 | 线程内 _counter 字段 | 对象头中的 Monitor |
| 精确唤醒 | unpark(Thread) 指定线程 | notify 随机/notifyAll 全部 |
| 信号丢失 | 不会(permit 持久化) | 会(先 notify 后 wait 丢失) |
| 中断行为 | 不抛异常,默默返回 | 抛 InterruptedException |
| 调用代价 | 更轻量(无锁竞争逻辑) | 较重(可能涉及锁升级) |
AQS 中 LockSupport 的典型用法
AQS 中 acquireQueued 方法的核心循环:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 关键:获取锁失败后 park 阻塞
if (shouldParkAfterFailedAcquire(p, node))
interrupted |= parkAndCheckInterrupt();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // 阻塞当前线程
return Thread.interrupted(); // 清除中断标志并返回
}这里有一个常被忽略的坑:parkAndCheckInterrupt() 返回后,interrupted 标志位已被清除。如果外部代码在 park() 被中断后直接返回而没有正确处理,线程可能无法感知到中断信号。这就是为什么 ReentrantLock 的 lockInterruptibly() 和 lock() 需要分开实现的根本原因——lock() 在中断后要重试继续等,lockInterruptibly() 在中断后要抛异常。
中断响应行为的底层差异
LockSupport.park() 对中断的响应是"默默返回"——它不会抛 InterruptedException,而是直接返回,同时清除中断标志位(Thread.interrupted() 会清除标志)。调用方必须手动检查中断状态。
这与 wait() 的行为不同:wait() 抛出 InterruptedException,调用方必须显式处理。
// LockSupport 的 park 中断后:线程继续执行,需要自己检查
LockSupport.park();
if (Thread.interrupted()) {
// 手动处理中断
System.out.println("被中断了,需要自行处理");
}生产踩坑:我见过一个线上故障,入口是 ThreadPoolExecutor 的 Worker 线程池。当线程池调用 shutdownNow() 时,对工作线程发了中断。但有一个 ForkJoinPool 的 ForkJoinWorkerThread 内部调用了 LockSupport.park(),中断后 park() 默默返回,但调用方没有检查 Thread.interrupted(),直接进入了下一轮循环再次 park()。结果线程池 shutdown 后该线程一直无法退出,导致应用无法正常关闭。排查时用 jstack 看到该线程处于 WAITING (parking) 状态且没有 expected termination 标志。
带超时的 park
LockSupport.parkNanos(long nanos) 和 parkUntil(long deadline) 可以实现定时阻塞:
// 等 500ms
LockSupport.parkNanos(500_000_000L);
// 等直到某个绝对时间点
LockSupport.parkUntil(System.currentTimeMillis() + 500);parkNanos 的底层实现也是通过 pthread_cond_timedwait 实现,区别在于:
parkNanos:相对时间,从调用时开始计时parkUntil:绝对时间,到某个时间点就返回
注意:parkNanos() 返回后不一定是超时了,也可能是被 unpark() 唤醒或因为中断返回。所以调用方需要判断条件是否满足,不能假设超时就表示条件已达成。
实际使用场景
AQS 同步器框架:ReentrantLock、CountDownLatch、Semaphore 等底层都用 LockSupport 实现线程阻塞和唤醒。核心是
acquireQueued()中的parkAndCheckInterrupt()。ForkJoinPool 工作窃取:工作线程在没有任务时调用
park()等待,被窃取线程通过unpark()唤醒。ForkJoinPool.scan()方法中,工作线程扫描自己的双端队列,如果没找到任务,就随机去偷别人的任务,都偷不到就park()。自定义阻塞队列:当需要精确控制线程的阻塞/唤醒时机时,LockSupport 比 wait/notify 更灵活。例如实现一个支持多消费者、每个消费者可以独立被唤醒的阻塞队列。
延迟队列实现:配合
parkNanos实现精确的定时唤醒,比如ScheduledThreadPoolExecutor的DelayedWorkQueue底层就用了parkNanos。Phaser:这个同步器的
arriveAndAwaitAdvance()内部也用 LockSupport(不是在循环中 wait/notify),因为 FJP 的线程需要精确唤醒。
完整对比表
| 维度 | LockSupport | Object.wait/notify | Condition.await/signal |
|---|---|---|---|
| 调用前提 | 无限制 | 必须持有 synchronized 锁 | 必须持有 Lock |
| 唤醒精度 | 精确到线程 | 随机/全部 | 精确到线程(signal) |
| 信号丢失 | 否(permit 持久化) | 是(先 notify 后 wait 丢失) | 否(await 在队列中) |
| 中断响应 | 默默返回,不抛异常 | 抛 InterruptedException | 抛 InterruptedException |
| 虚假唤醒 | 有(底层 pthread_cond_wait) | 有 | 有 |
| 是否释放锁 | 否(不涉及锁) | 是(释放对象锁) | 是(释放 Lock) |
| 底层实现 | PlatformEvent (pthread_cond) | ObjectMonitor | AbstractQueuedSynchronizer.ConditionObject |
| 典型使用 | AQS 框架、ForkJoinPool | 传统 synchronized 块 | ReentrantLock 配合 |
| 使用频率 | 底层框架代码 | 业务代码 | 过渡方案 |
代码示例
1. LockSupport 实现生产者-消费者(精确唤醒版)
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.LockSupport;
public class LockSupportDemo {
private static final Queue<String> queue = new LinkedList<>();
private static final int CAPACITY = 5;
private static volatile boolean running = true;
private static volatile boolean producerBlocked = false;
private static volatile boolean consumerBlocked = false;
private static Thread producerThread;
private static Thread consumerThread;
public static void main(String[] args) throws InterruptedException {
producerThread = new Thread(() -> {
int seq = 0;
while (running) {
synchronized (queue) {
while (queue.size() == CAPACITY) {
producerBlocked = true;
// 释放锁后 park,避免死锁
}
// 出同步块后再 park,演示 LockSupport 不需要锁的特性
}
// 队列满时 park,不需要持有锁
if (producerBlocked) {
LockSupport.park();
producerBlocked = false;
}
synchronized (queue) {
queue.offer("msg-" + seq++);
System.out.println("生产: msg-" + (seq-1) + " 队列大小: " + queue.size());
// 如果有消费者在等,精确唤醒
if (consumerBlocked) {
LockSupport.unpark(consumerThread);
consumerBlocked = false;
}
}
sleep(200);
}
});
consumerThread = new Thread(() -> {
while (running) {
synchronized (queue) {
while (queue.isEmpty()) {
consumerBlocked = true;
}
}
if (consumerBlocked) {
LockSupport.park();
consumerBlocked = false;
}
synchronized (queue) {
String msg = queue.poll();
System.out.println("消费: " + msg + " 队列大小: " + queue.size());
if (producerBlocked) {
LockSupport.unpark(producerThread);
producerBlocked = false;
}
}
sleep(500);
}
});
producerThread.start();
consumerThread.start();
Thread.sleep(5000);
running = false;
// 唤醒可能还在等待的线程
LockSupport.unpark(producerThread);
LockSupport.unpark(consumerThread);
producerThread.join();
consumerThread.join();
}
// 2. 演示 LockSupport 的 permit 机制
public static void demonstratePermit() {
Thread t = new Thread(() -> {
System.out.println("线程启动,准备 park");
LockSupport.park();
System.out.println("park 返回,继续执行");
// 再次 park - 因为 permit 已经被消费,这里会阻塞
System.out.println("再次 park 会阻塞,但先让主线程 unpark");
LockSupport.park();
System.out.println("第二次 park 返回");
});
t.start();
// 先 unpark,演示 permit 机制
LockSupport.unpark(t);
System.out.println("主线程提前 unpark");
sleep(1000);
LockSupport.unpark(t);
System.out.println("主线程再次 unpark");
}
// 3. 演示 parkNanos 的超时等待
public static void demonstrateParkNanos() {
Thread t = new Thread(() -> {
System.out.println("开始等待 500ms");
long start = System.nanoTime();
LockSupport.parkNanos(500_000_000L);
long elapsed = System.nanoTime() - start;
System.out.println("等待结束,实际经过 " + (elapsed / 1_000_000) + "ms");
});
t.start();
}
private static void sleep(long ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}运行结果分析
主线程提前 unpark
线程启动,准备 park
park 返回,继续执行
再次 park 会阻塞,但先让主线程 unpark
主线程再次 unpark
第二次 park 返回注意第一行输出顺序:主线程先调 unpark(t),此时 t 还没执行到 park(),但 permit 已经被置为 1。当 t 执行 park() 时,发现 permit 为 1,直接消费并返回,不会阻塞。这就是 permit 机制解决时序问题的关键。
常见踩坑
1. park 返回后不一定是 unpark 了
park() 返回有三种可能:
- 被
unpark()唤醒 - 被中断(
Thread.interrupt()) - 虚假唤醒(spurious wakeup,但概率极低)
所以 park 的调用必须放在条件循环中:
// 错误的写法
while (!condition) {
LockSupport.park(); // 如果被虚假唤醒,可能 condition 还没满足
}
// 正确的写法
while (!condition) {
LockSupport.park();
}2. park 调用前后的线程状态
park() 阻塞时,线程状态是 WAITING (parking),jstack 中能看到:
"pool-1-thread-1" #11 prio=5 os_prio=0 tid=0x00007f1234000000 nid=0x2a3b waiting on condition [0x00007f122fbfe000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)"WAITING (parking)" 是 LockSupport 特有的状态,区别于 wait() 的 "WAITING (on object monitor)"。
3. park 和 unpark 的线程安全性
LockSupport.unpark(Thread t) 可以在任意线程调用,不要求当前线程持有任何锁。这是 wait/notify 做不到的——notify() 必须在 synchronized 块中调用。
但注意:unpark() 内部的 pthread_cond_signal 本身不是线程安全的,HotSpot 通过 _mutex 保护了 _counter 的读写。所以 unpark() 本身是线程安全的,但调用方仍需保证业务逻辑的一致性。
4. 不能用于替代 wait/notify 的全部场景
LockSupport 不释放任何锁。如果你在 synchronized 块内 park(),其他线程要想获取同一个锁,也会被阻塞。这个场景下应该用 wait() 而非 park()。
总结
LockSupport 是 Java 并发编程中的底层基础设施,它比 wait/notify 更灵活、更精确、更安全。核心要点:
- permit 机制:每个线程关联一个二元许可,
unpark最多将 permit 置为 1,park消费 permit。这从根本上解决了先 notify 后 wait 的时序丢失问题。 - 不需要同步块:
park()不释放锁,也不要求调用方持有锁,使用门槛更低。 - 精确唤醒:
unpark(Thread t)可以精确唤醒指定线程,不像notify()那样随机。 - 中断响应:
park()对中断默默返回,调用方需自行检查中断状态。 - 生产实践:AQS、ForkJoinPool、ReentrantLock 等核心组件都依赖 LockSupport,理解它是理解 JUC 框架的钥匙。
- 底层差异:Linux 基于 pthread_cond_wait 实现,macOS 基于 Mach 线程原语,Windows 基于 Event 对象。了解这些差异有助于跨平台调试。
参考资料:
- 《Java 并发编程的艺术》第 5 章
- JDK 源码:
java.util.concurrent.locks.LockSupport - AQS 源码:
AbstractQueuedSynchronizer.acquireQueued() - HotSpot 源码:
src/hotspot/os/linux/os_linux.cpp中的PlatformEvent实现