LockSupport 详解及源码分析
一、LockSupport 概述
LockSupport 是 Java 并发包(java.util.concurrent.locks
)中的核心工具类,提供线程阻塞和唤醒的基本能力。与传统的 wait/notify
机制相比,LockSupport 具有以下优势:
- 无需同步块:可在任意位置调用
- 更精准控制:以线程为操作对象
- 许可机制:支持先唤醒后阻塞
- 中断处理:响应中断但不抛出异常
LockSupport 是构建高级同步器(如 ReentrantLock、CountDownLatch 等)的基础。
二、核心方法与功能
方法签名 | 功能描述 |
---|---|
park() | 阻塞当前线程 |
park(Object blocker) | 阻塞当前线程,设置阻塞对象(用于诊断) |
parkNanos(long nanos) | 阻塞指定纳秒数 |
parkUntil(long deadline) | 阻塞直到指定时间戳 |
unpark(Thread thread) | 唤醒指定线程 |
三、许可机制(Permit)
LockSupport 的核心是 许可机制:
- 每个线程关联一个 二元许可(0 或 1)
unpark
:将许可设为 1(若已是 1 则保持)park
:消耗许可(设为 0)并立即返回(若许可为 1)- 若许可为 0,则阻塞线程
关键特性:
- 先
unpark
后park
不会阻塞 - 多次
unpark
效果等同于一次 - 许可不累积(最大为 1)
四、源码分析(基于 OpenJDK 17)
1. Unsafe 操作入口
public class LockSupport {
// 获取 Unsafe 实例
private static final Unsafe U = Unsafe.getUnsafe();
// Thread 类中 parkBlocker 字段的偏移量
private static final long PARKBLOCKER;
static {
try {
PARKBLOCKER = U.objectFieldOffset
(Thread.class.getDeclaredField("parkBlocker"));
} catch (ReflectiveOperationException e) {
throw new Error(e);
}
}
private LockSupport() {} // 私有构造,无法实例化
}
2. park() 核心实现
// 阻塞当前线程
public static void park() {
U.park(false, 0L);
}
// 带阻塞对象的 park
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker); // 设置阻塞对象
U.park(false, 0L); // 调用 unsafe 的 park
setBlocker(t, null); // 清除阻塞对象
}
// 设置阻塞对象(通过 Unsafe 直接操作线程内存)
private static void setBlocker(Thread t, Object arg) {
U.putObject(t, PARKBLOCKER, arg);
}
3. unpark() 实现
// 唤醒指定线程
public static void unpark(Thread thread) {
if (thread != null) {
U.unpark(thread);
}
}
4. 带超时的 park 方法
// 阻塞指定纳秒数
public static void parkNanos(long nanos) {
if (nanos > 0) {
U.park(false, nanos);
}
}
// 阻塞直到绝对时间(毫秒)
public static void parkUntil(long deadline) {
U.park(true, deadline);
}
五、执行流程图
六、使用示例
1. 基础用法
Thread thread = new Thread(() -> {
System.out.println("线程即将阻塞");
LockSupport.park(); // 阻塞当前线程
System.out.println("线程被唤醒");
});
thread.start();
Thread.sleep(1000);
LockSupport.unpark(thread); // 唤醒线程
2. 先唤醒后阻塞
Thread thread = new Thread(() -> {
try {
Thread.sleep(2000); // 确保先执行 unpark
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行 park");
LockSupport.park();
System.out.println("park 立即返回");
});
thread.start();
LockSupport.unpark(thread); // 先提供许可
3. 使用阻塞对象(诊断友好)
Object blocker = new Object();
Thread thread = new Thread(() -> {
LockSupport.park(blocker); // 设置阻塞对象
});
thread.start();
// jstack 输出片段示例:
// "Thread-0" #1 prio=5 os_prio=0 tid=0x00007f... nid=0x6d03 waiting on condition [0x00007f...]
// java.lang.Thread.State: WAITING (parking)
// at sun.misc.Unsafe.park(Native Method)
// - parking to wait for <0x000000076b6080c8> (a java.lang.Object)
七、与 wait/notify 的对比
特性 | LockSupport | wait/notify |
---|---|---|
同步要求 | 不需要同步块 | 必须在同步块内使用 |
操作对象 | 直接操作线程 | 操作对象监视器 |
唤醒顺序 | 精确唤醒指定线程 | 随机唤醒一个等待线程 |
先唤醒后阻塞 | 支持 | 不支持 |
中断处理 | 返回但不抛出异常 | 抛出 InterruptedException |
许可机制 | 有 | 无 |
超时控制 | 支持纳秒级精度 | 仅支持毫秒级 |
八、核心源码解析(Native 层)
1. park/unpark 的底层实现(Linux 版本)
// 在 HotSpot 的 os_linux.cpp 中
void Parker::park(bool isAbsolute, jlong time) {
if (Atomic::xchg(0, &_counter) > 0) return;
// 获取当前线程
Thread* thread = Thread::current();
// 检查中断状态
if (thread->is_interrupted(false)) {
return;
}
// 设置超时时间
timespec absTime;
if (time > 0) {
to_abstime(&absTime, time, isAbsolute);
}
// 循环检查
while (_counter < 1) {
// 挂起线程(通过 futex 系统调用)
int status = pthread_cond_timedwait(&_cond, &_mutex, &absTime);
// 处理超时或中断
if (status == ETIME || (status == EINTR && _counter < 1)) {
break;
}
}
// 重置计数器
_counter = 0;
}
void Parker::unpark() {
// 原子增加计数器
if (Atomic::xchg(1, &_counter) > 0) return;
// 唤醒等待的线程
pthread_cond_signal(&_cond);
}
2. 关键实现点:
- 原子计数器:使用
_counter
实现许可机制 - 条件变量:通过
pthread_cond_timedwait
实现超时等待 - futex 系统调用:Linux 下的高效线程同步原语
- 中断处理:检查线程中断状态后直接返回
九、最佳实践与注意事项
-
避免永久阻塞:
// 推荐使用超时版本 LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(5));
-
结合中断使用:
while (!Thread.currentThread().isInterrupted()) { LockSupport.park(this); // 清除中断状态后继续 Thread.interrupted(); }
-
资源释放保证:
try { LockSupport.park(); } finally { // 确保资源释放 }
-
诊断优化:
// 使用阻塞对象提高诊断可读性 LockSupport.park(lockObject);
十、性能特点
- 低开销:比 synchronized 轻量 20-30%
- 无竞争优化:在无竞争情况下几乎无开销
- 系统调用优化:Linux 下使用 futex 避免不必要的内核切换
- 缓存友好:许可状态使用单个变量,缓存效率高
总结
LockSupport 是 Java 并发编程的底层基石:
- 基于 许可机制 实现线程阻塞/唤醒
- 提供 无锁、精准 的线程控制能力
- 支持 先唤醒后阻塞 的灵活模式
- 通过 Unsafe 直接操作内存 保证高效性
- 为 AQS 等高级同步组件提供基础支持
理解 LockSupport 的工作原理对于掌握 Java 并发模型至关重要,它解决了传统 wait/notify 机制的诸多限制,为构建高性能并发组件提供了坚实基础。