引言:并发编程的“瑞士军刀”
在 Java 并发编程的浩瀚宇宙中,我们熟知 synchronized关键字、ReentrantLock显式锁以及各种并发集合。然而,在这些高楼大厦之下,埋藏着一位低调但至关重要的基石工匠——LockSupport 工具类。
java.util.concurrent.locks.LockSupport自 JDK 1.5 引入,是一个用于创建锁和其他同步工具的基础线程阻塞工具类。它提供了线程阻塞(parking)和唤醒(unparking) 的核心原语,其强大之处在于它直接作用于线程本身,并且具有许可证(permit) 的巧妙设计。LockSupport是构建高级同步组件(如 AQS、ReentrantLock、CountDownLatch等)的底层基础,堪称 “并发工具之母”。
理解 LockSupport,不仅可以帮助我们编写更灵活、高效的并发控制代码,更能让我们洞悉 Java 并发框架的设计哲学和底层机制。本文将深入解析 LockSupport的工作原理、核心 API 及其在实战中的应用。
一、 核心机制:许可证(Permit)模型
LockSupport的核心是一个隐式的信号量机制,称为许可证(permit)。这个模型非常简洁但极其强大:
-
许可证数量:每个线程关联一个许可证,但许可证的计数最多只能为 1。你可以将其理解为一个只能存放一张令牌的盒子。
-
park():如果调用时许可证可用(>0),则立即消耗该许可证并返回,线程继续执行;如果许可证不可用(=0),则阻塞当前线程,进入 WAITING 状态。 -
unpark(Thread thread):如果指定线程的许可证尚未可用(=0),则使其可用(设置为1)。如果该线程正因为park()而阻塞,则立即唤醒它;如果指定线程的许可证已经可用(=1),则调用无效,许可证保持为1(因为最多只能为1)。
这个“一次性”的许可证机制,使得 unpark操作可以先于 park操作发生。这是 LockSupport相比传统 wait/notify机制最革命性的优势之一,它从根本上解决了竞态条件问题。
二、 API 详解
LockSupport的 API 非常精简,主要包含以下几类方法:
2.1 核心阻塞与唤醒
-
static void park():阻塞当前线程,除非许可证可用。 -
static void unpark(Thread thread):使给定线程的许可证可用(如果尚未可用),并立即唤醒它(如果它正被阻塞)。
2.2 高级阻塞(支持阻塞条件与超时)
-
static void parkNanos(long nanos):阻塞当前线程最多不超过指定的纳秒数,除非许可证可用。 -
static void parkUntil(long deadline):阻塞当前线程直到指定的截止时间(自1970年以来的毫秒数),除非许可证可用。 -
static void park(Object blocker):功能同park(),但允许提供一个 blocker对象,用于问题诊断和监控。 -
static void parkNanos(Object blocker, long nanos) -
static void parkUntil(Object blocker, long deadline)
blocker参数的重要性:当线程被阻塞时,这个对象会被记录在线程的 parkBlocker字段中。通过 JDK 提供的监控工具(如 jstack),可以清晰地看到线程正在等待哪个资源,极大地方便了并发问题的诊断。
示例:使用 jstack诊断
"main" #1 prio=5 os_prio=0 tid=0x00007f... nid=0x... waiting on condition [0x...]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000076b4d0e80> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) # 关键信息!
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
...
如果没有传递 blocker,这里将显示 - parking to wait for <no object>,使得诊断变得困难。
三、 对比传统 wait/notify 机制
为了更直观地理解 LockSupport的现代性和优势,我们通过一个表格将其与传统的 wait/notify进行对比:
|
特性 |
|
|
|---|---|---|
|
作用对象 |
必须在同步块(synchronized) 内操作特定对象的监视器。 |
直接操作线程,与任何锁对象无关。 |
|
调用顺序 |
严格要求先 |
顺序无关。 |
|
精确唤醒 |
|
|
|
中断响应 |
|
|
|
灵活性 |
较低,必须获取监视器锁。 |
极高,可以在任何地方调用。 |
|
许可证机制 |
无 |
有,核心设计理念。 |
四、 实战应用与源码解析
4.1 基本用法示例
下面通过几个代码示例来展示 LockSupport的基本用法。
示例 1:基础 park/unpark
public class LockSupportDemo1 {
public static void main(String[] args) throws InterruptedException {
Thread mainThread = Thread.currentThread();
// 创建一个工作者线程
Thread worker = new Thread(() -> {
try {
System.out.println("Worker: 开始工作,休息 2 秒后唤醒主线程");
Thread.sleep(2000); // 模拟工作耗时
System.out.println("Worker: 准备唤醒主线程");
LockSupport.unpark(mainThread); // 先执行 unpark,发放许可证
} catch (InterruptedException e) {
e.printStackTrace();
}
});
worker.start();
System.out.println("Main: 即将被阻塞");
LockSupport.park(); // 然后主线程 park,但此时许可证已可用,不会阻塞
System.out.println("Main: 被成功唤醒");
}
}
输出:
Main: 即将被阻塞
Worker: 开始工作,休息 2 秒后唤醒主线程
Worker: 准备唤醒主线程
Main: 被成功唤醒
这个例子展示了 unpark先于 park发生的正确性。
示例 2:使用 blocker 参数
public class LockSupportDemo2 {
private static final Object LOCK = new Object();
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("Thread: 即将阻塞,并设置 blocker");
// 使用 LOCK 对象作为 blocker,方便监控
LockSupport.park(LOCK);
System.out.println("Thread: 被唤醒");
});
t.start();
try {
Thread.sleep(1000); // 确保线程 t 先进入 park 状态
// 使用 jstack 此时可以看到线程在等待 LOCK 对象
LockSupport.unpark(t);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4.2 源码级解析:构建一个简单的互斥锁
LockSupport是 AbstractQueuedSynchronizer (AQS)的基石,而 AQS 又是 ReentrantLock等锁的基础。我们可以用 LockSupport模拟一个非常简单的互斥锁,以理解其底层原理。
/**
* 一个使用 LockSupport 实现的极其简单的互斥锁
* 注意:此为非可重入锁,仅用于教学演示,生产环境请使用 ReentrantLock
*/
public class SimpleMutex {
// 锁的拥有者
private volatile Thread owner;
// 等待队列(简单用链表实现)
private volatile LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue<>();
public void lock() {
// 尝试获取锁
if (tryLock()) {
return;
}
// 获取失败,将当前线程加入等待队列
Thread current = Thread.currentThread();
waiters.offer(current);
// 自旋或阻塞,直到成为队列头部且成功获取到锁
while (true) {
// 检查是否轮到本线程(队首)
if (waiters.peek() == current && tryLock()) {
waiters.poll(); // 成功获取,将自己从队列移除
return;
}
// 不是队首或获取失败,则阻塞自己
LockSupport.park(this); // 使用 this 作为 blocker
// 被唤醒后继续循环检查
}
}
public boolean tryLock() {
// CAS 操作,将 owner 从 null 设置为当前线程
if (owner == null) {
synchronized (this) {
if (owner == null) {
owner = Thread.currentThread();
return true;
}
}
}
return false;
}
public void unlock() {
// 只有锁的持有者才能解锁
if (owner != Thread.currentThread()) {
throw new IllegalMonitorStateException();
}
owner = null; // 释放锁
// 唤醒等待队列中的第一个线程(如果存在)
Thread next = waiters.peek();
if (next != null) {
LockSupport.unpark(next);
}
}
}
代码解析:
-
lock():线程尝试获取锁。失败则加入等待队列,并调用LockSupport.park(this)阻塞自己。 -
unlock():当前线程释放锁,然后取出等待队列的头节点线程,调用LockSupport.unpark(next)精确唤醒它。 -
被唤醒的线程会从
park()方法返回,继续while循环,再次尝试获取锁 (tryLock())。
这个简化的模型清晰地展示了 LockSupport如何与一个等待队列配合,实现线程的精准阻塞和唤醒,这正是 AQS核心思想的雏形。
五、 高级特性与注意事项
5.1 对中断的响应
LockSupport.park()方法会响应线程中断,但方式很特别:它不会抛出 InterruptedException,而是立即返回,并且会清除线程的中断状态。因此,调用者需要手动检查中断状态。
public void parkResponseToInterrupt() {
LockSupport.park();
if (Thread.interrupted()) { // 检查并清除中断状态
System.out.println("线程被中断唤醒,可能需要进行中断处理");
// 处理中断逻辑,或者再次抛出 InterruptedException
} else {
System.out.println("线程被 unpark 正常唤醒");
}
}
5.2 底层实现:Unsafe 类
LockSupport的 park和 unpark方法实际上是 sun.misc.Unsafe类中本地方法(Native Methods)的代理。Unsafe提供了直接操作内存和线程的底层能力,这也是 LockSupport高效的原因。
// LockSupport.java 源码片段
public static void park() {
UNSAFE.park(false, 0L);
}
public static void unpark(Thread thread) {
if (thread != null) {
UNSAFE.unpark(thread);
}
}
六、 总结

LockSupport是一个看似简单但内涵极其丰富的并发工具类。它提供的 “许可证”模型和线程阻塞/唤醒原语,是构建现代 Java 并发框架的原子砖石。
-
核心价值:解决了传统
wait/notify机制的顺序性约束和唤醒不精确问题,提供了更灵活、更可靠的线程间协作方式。 -
应用场景:它是
AQS及所有基于 AQS 的同步器(如ReentrantLock,Semaphore,CountDownLatch)的底层实现基础。在需要精确控制线程阻塞与唤醒的自定义同步工具开发中,它是无可替代的选择。 -
最佳实践:
-
总是优先考虑使用
park(Object blocker)重载方法,传入有意义的blocker对象,以便于监控和诊断。 -
理解其响应中断的独特方式,并在代码中妥善处理中断。
-
深刻理解其“许可证明”机制,尤其是
unpark先于park有效的特性,这是避免很多并发陷阱的关键。
-
掌握 LockSupport,意味着你不仅学会了一个 API,更是向 Java 并发编程的圣殿迈进了关键一步,真正开始理解其底层运作的魔法。

被折叠的 条评论
为什么被折叠?



