《LockSupport 与线程通信机制:从 park/unpark 到底层实现》


一、前言:为什么要了解 LockSupport

在并发编程中,线程通信的核心就是“挂起与唤醒”。
传统方式如:

  • Object.wait()/notify()
  • Condition.await()/signal()
    都必须在锁内使用,不够灵活。

LockSupport 则是 JUC 的底层工具类,
提供更底层、线程级别的挂起与唤醒机制,
它是:

  • AQS 的核心原语
  • FutureTask、ReentrantLock、Condition 等的基础实现

一句话:LockSupport 是所有高层同步工具的“线程调度引擎”。


二、核心原理与工作机制

(1)主要方法
LockSupport.park();          // 挂起当前线程
LockSupport.park(Object blocker); // 挂起并记录阻塞对象
LockSupport.unpark(Thread t);     // 唤醒指定线程
(2)工作逻辑

LockSupport 基于 许可证(Permit)机制

  • 每个线程默认有 0 个许可
  • unpark() 向线程发放一个许可;
  • 若线程随后调用 park(),则立即返回;
  • 若线程先调用 park(),则被阻塞直到获得许可。

小结:

park() → 消费一个许可;
unpark() → 发放一个许可;
多次 unpark() 不会累积许可(最多 1 个)。

(3)与 Object.wait() 区别
对比项 LockSupport Object.wait()
是否依赖锁 ❌ 否 ✅ 必须在 synchronized 内
唤醒方式 unpark(线程) notify()/notifyAll()
唤醒目标 精确唤醒某个线程 唤醒等待队列中任意线程
底层机制 Unsafe.park/unpark JVM Monitor 机制
是否易用 ✅ 更灵活 ❌ 需锁配合

三、底层实现与 Unsafe 机制

(1)核心实现

LockSupport 内部依赖 Unsafe 类:

public static void park() {
    Unsafe.getUnsafe().park(false, 0L);
}

public static void unpark(Thread thread) {
    if (thread != null)
        Unsafe.getUnsafe().unpark(thread);
}
(2)底层原理

Unsafe.park() 通过操作 操作系统级的线程阻塞队列 实现挂起:

  • 当线程执行 park() 时进入阻塞状态;
  • 操作系统不再调度它(节省 CPU);
  • unpark() 调用时,系统唤醒该线程。

线程状态变化:

RUNNABLE → WAITING → RUNNABLE

这套机制比传统的自旋等待或 busy-wait 更高效、节能。

(3)JVM 层支持

JVM 内部通过 Parker 对象(C++ 实现)管理每个线程的许可状态。
源码位于 OpenJDK unsafe.cpp

void Parker::park(bool isAbsolute, jlong time) {
    // 线程进入等待队列
    if (counter == 0)
        _pthread_cond_wait(&cond, &mutex);
    else
        counter = 0;
}

四、在 AQS 与并发组件中的作用

LockSupport 是整个 JUC 框架的“阻塞唤醒引擎”。
所有基于 AQS 的同步器(如 ReentrantLock、Semaphore、FutureTask)
都在内部使用 park/unpark 控制线程状态。

(1)AQS 等待队列逻辑

当线程获取锁失败时:

if (!tryAcquire(arg)) {
    addWaiter(Node.EXCLUSIVE);
    LockSupport.park(this);
}

→ 线程被挂起
→ 当前线程进入等待队列
→ 当锁释放后,调用 unpark() 唤醒下一个线程。

(2)FutureTask 示例

在 FutureTask 的 get() 方法中:

while (state < COMPLETED) {
    LockSupport.park(this);
}

任务完成时:

LockSupport.unpark(waiterThread);

所以,无论是锁竞争、条件等待还是异步结果获取,
最终都离不开 LockSupport 的线程调度机制。


五、常见用法与注意事项

(1)基本使用示例
Thread t = new Thread(() -> {
    System.out.println("子线程等待许可...");
    LockSupport.park();
    System.out.println("子线程被唤醒!");
});
t.start();

Thread.sleep(1000);
System.out.println("主线程发放许可");
LockSupport.unpark(t);

输出:

子线程等待许可...
主线程发放许可
子线程被唤醒!
(2)先 unpark 后 park 也可生效
LockSupport.unpark(t); // 发放许可
t.start();             // 后启动线程

因为线程的“许可”是可记忆的(一次性),
线程启动后调用 park() 时会立即返回,不阻塞。

(3)避免误用
  • 不可使用多次 unpark() 叠加(只保留一次许可);
  • 不可用 interrupt() 代替 unpark()(会清除中断标志);
  • 不应混用 wait/notify 与 park/unpark(不同机制)。

六、面试常考问题与总结

问题 答案要点
Q1:LockSupport 的作用? 提供底层线程挂起与唤醒机制,支撑 AQS、FutureTask 等组件。
Q2:park/unpark 与 wait/notify 区别? 前者不依赖锁,可精准唤醒指定线程;后者需 synchronized,唤醒随机线程。
Q3:为什么 LockSupport 比 wait/notify 效率高? 不依赖监视器锁,操作系统级挂起,避免上下文切换开销。
Q4:park() 线程如何恢复? 被 unpark() 唤醒、被中断、或发生虚假唤醒。
Q5:多次 unpark 会积累许可吗? 不会,只保留一个许可。
Q6:AQS 为什么使用 LockSupport 而不是 wait? 因为 AQS 不一定持有对象锁,且需要精确唤醒队列中的线程。

结语

LockSupport 是并发框架的“地基”,
所有线程等待与唤醒的实现最终都基于它完成。

理解 LockSupport
就等于理解了 AQS、ReentrantLock、FutureTask、Condition 的运行本质。

下一篇,我将写——
《Condition 接口与等待唤醒机制:对标 Object.wait/notify 的精准控制》
作为锁通信机制的最后篇,深入讲清“线程协作”的设计哲学。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐