- 一、 Java 到 JVM 内核的 JNI 桥接路径
- 1. JNI 入口分析 (`prims/unsafe.cpp`)
- 二、 `Parker` 对象的类结构与内存布局
- 1. `PlatformParker` 与 `Parker` 继承链 (`os/linux/vm/os_linux.hpp`)
- 2. 字段设计解析
- 三、 `Parker::park()` C++ 源码深挖
- 四、 `Parker::unpark()` C++ 源码深挖
- 五、 Linux `pthread` 库与 Kernel Futex 交互机制
- 1. `pthread_cond_wait` 的原子解锁与挂起
- 2. `pthread_cond_signal` 的唤醒过程
- 3. 时钟选型:`CLOCK_MONOTONIC` vs `CLOCK_REALTIME`
- 六、 许可机制 (`_counter`) 的物理运行状态图解
- 场景 A:先 `park()`,后 `unpark()`
- 场景 B:先 `unpark()`,后 `park()`(许可预置)
- 七、 HotSpot 线程同步原语纵向对比
前言
本文旨在记录近期研读Java源码的学习心得与疑难问题。由于个人理解水平有限,文中内容难免存在疏漏,恳请读者不吝指正。
LockSupport源码剖析
一、 Java 到 JVM 内核的 JNI 桥接路径
在 Java 层面,LockSupport.park() 和 unpark() 最终通过 sun.misc.Unsafe 的 native 方法进入 JVM 内核。
Java 视角 JVM 内核视角 (HotSpot C++) Linux 内核视角
LockSupport.park()
└─> Unsafe.park() ------------> unsafe.cpp: Unsafe_Park()
└─> Parker::park() --------------------> pthread_cond_wait() / futex
LockSupport.unpark()
└─> Unsafe.unpark() ----------> unsafe.cpp: Unsafe_Unpark()
└─> Parker::unpark() ------------------> pthread_cond_signal() / futex
1. JNI 入口分析 (prims/unsafe.cpp)
在 OpenJDK 8源码的 hotspot/src/share/vm/prims/unsafe.cpp 中,Unsafe_Park 与 Unsafe_Unpark 实现了 JNI 的绑定逻辑:
// hotspot/src/share/vm/prims/unsafe.cpp
UNSAFE_ENTRY(void, Unsafe_Park(JNIEnv *env, jobject unsafe, jboolean isAbsolute, jlong time))
UnsafeWrapper("Unsafe_Park");
// 1. 设置 Java 线程的中断响应标识
JavaThread* thread = JavaThread::thread_from_jni_environment(env);
// 2. 检查线程是否已经被中断,或者已经具备许可
// 如果已触发中断,直接返回,不再阻塞
if (thread->is_interrupted(true, true)) {
return;
}
// 3. 时间参数校验与转换
jlong ns = 0;
if (time > 0) {
if (isAbsolute) {
// 绝对时间 (毫秒级):如 LockSupport.parkUntil
ns = time;
} else {
// 相对时间 (纳秒级):如 LockSupport.parkNanos
ns = time;
}
}
// 4. 线程状态切换标志:设置当前线程处于 BlockInVM 状态
JavaThreadParkedState jtps(thread, time);
// 5. 获取当前 JavaThread 关联的 Parker 对象,并调用 C++ 的 park 方法
thread->parker()->park(isAbsolute != 0, time);
// 6. 清理 park 状态
if (thread->is_interrupted(true, true)) {
// 再次确认中断标志
}
UNSAFE_END
UNSAFE_ENTRY(void, Unsafe_Unpark(JNIEnv *env, jobject unsafe, jobject jthread))
UnsafeWrapper("Unsafe_Unpark");
Parker* p = NULL;
if (jthread != NULL) {
// 1. 通过 OOP 拿到目标 Java 线程对象
oop java_thread = JNIHandles::resolve_non_null(jthread);
if (java_thread != NULL) {
// 2. 将 Java 线程转为 C++ 内核中的 JavaThread 对象
JavaThread* thr = java_lang_Thread::thread(java_thread);
if (thr != NULL) {
// 3. 提取关联的 Parker 指针
p = thr->parker();
}
}
}
// 4. 执行 C++ 层的 unpark 唤醒逻辑
if (p != NULL) {
p->unpark();
}
UNSAFE_END
二、 Parker 对象的类结构与内存布局
在 HotSpot 中,每一个 JavaThread 实例在创建时,都会在 C++ 层面同步分配一个 Parker 对象实例,二者是一对一的强绑定关系(通过 thread->parker() 访问)。
1. PlatformParker 与 Parker 继承链 (os/linux/vm/os_linux.hpp)
在 Linux 平台下,Parker 继承自 os::PlatformParker,底层直接封装了 POSIX 线程库(pthread)的互斥锁(pthread_mutex_t)与条件变量(pthread_cond_t)。
// hotspot/src/os/linux/vm/os_linux.hpp
class PlatformParker : public CHeapObj<mtInternal> {
protected:
enum {
REL_INDEX = 0,
ABS_INDEX = 1
};
// POSIX 互斥锁与条件变量数组
// _cond[0] 用于相对时间 (CLOCK_MONOTONIC)
// _cond[1] 用于绝对时间 (CLOCK_REALTIME)
pthread_mutex_t _mutex[1];
pthread_cond_t _cond[2];
public:
PlatformParker();
~PlatformParker();
};
// hotspot/src/share/vm/runtime/park.hpp
class Parker : public os::PlatformParker {
private:
volatile int _counter; // 【核心许可计数器】:0 表示无许可,1 表示有许可
Parker * FreeNext;
JavaThread * _curparker;
public:
Parker() : PlatformParker() {
_counter = 0;
_curparker = NULL;
}
void park(bool isAbsolute, jlong time);
void unpark();
// CAS 原子递减/设置 counter
int offset_in_bytes(){ return offset_of(Parker, _counter); }
};
2. 字段设计解析
volatile int _counter:这是LockSupport状态的核心。与操作系统信号量不同,_counter的上限为1,且下限为0。它表示许可(Permit)的数量,不可累加。pthread_mutex_t _mutex[1]:用于保护_counter状态变更与pthread_cond_wait的原子互斥锁。pthread_cond_t _cond[2]:条件变量。用于挂起和唤醒 Linux 线程。
三、 Parker::park() C++ 源码深挖
Parker::park() 实现了原子抢占许可 → 状态转换 → 阻塞挂起 → Safepoint 检查的完整闭环。
// hotspot/src/os/linux/vm/os_linux.cpp
void Parker::park(bool isAbsolute, jlong time){
// =========================================================================
// 第一阶段:Fast-Path (快速路径) - 避免进入内核态
// =========================================================================
// 1. 如果当前 _counter > 0,说明此前已经执行过 unpark()。
// 利用 CAS 原子重置 _counter 为 0 并直接返回,无需进行昂贵的互斥锁加锁与线程挂起
if (Atomic::xchg(0, &_counter) > 0) return;
JavaThread *jt = (JavaThread *)Thread::current();
// 2. 如果当前线程已被打上中断标记,直接返回
if (Thread::is_interrupted(jt, false)) {
return;
}
// 3. 计算绝对超时时间结构体 (timespec)
struct timespec absTime;
if (time > 0) {
unpackTime(&absTime, isAbsolute, time);
}
// =========================================================================
// 第二阶段:线程状态切换 (HotSpot 安全点保护机制)
// =========================================================================
// 创建 ThreadBlockInVM RAII 对象:
// 将当前 JavaThread 状态由 _thread_in_vm 切换为 _thread_blocked。
// 这告知 JVM 的 GC 线程:“当前线程已阻塞在 native 挂起流程中,可以安全触发 Safepoint/GC”。
ThreadBlockInVM tbivm(jt);
// 如果在此期间发生了中断,或者尝试获取 mutex 锁失败,直接返回
if (Thread::is_interrupted(jt, false) || pthread_mutex_trylock(_mutex) != 0) {
return;
}
int status;
// =========================================================================
// 第三阶段:再次校验 _counter (Slow-Path 内双重检查)
// =========================================================================
if (_counter > 0) {
// 获取锁后发现 _counter 被 unpark 改为了 1,扣减许可并直接释放锁返回
_counter = 0;
pthread_mutex_unlock(_mutex);
OrderAccess::fence(); // 插入全内存屏障保障可见性
return;
}
// 设置当前 Parker 正在被哪个 JavaThread 挂起
_curparker = jt;
// =========================================================================
// 第四阶段:陷入 Linux 内核,真正挂起线程
// =========================================================================
if (time == 0) {
// 永久阻塞:调用 Linux pthread 库的 pthread_cond_wait
// 过程:原子性地释放 _mutex 锁,同时让线程进入 TASK_UNINTERRUPTIBLE / TASK_INTERRUPTIBLE 状态
status = pthread_cond_wait(_cond, _mutex);
} else {
// 超时阻塞:根据绝对时间/相对时间调用 pthread_cond_timedwait
status = os::Linux::safe_cond_timedwait(_cond, _mutex, &absTime);
}
// =========================================================================
// 第五阶段:唤醒后的恢复与状态清空
// =========================================================================
_counter = 0; // 清除许可状态
_curparker = NULL;
// 释放互斥锁
pthread_mutex_unlock(_mutex);
// 插入内存屏障,防止指令重排
OrderAccess::fence();
// 退出 ThreadBlockInVM 作用域:
// 线程状态由 _thread_blocked 变更为 _thread_in_vm。
// 【关键】:在转换回 _thread_in_vm 时,HotSpot 会检查当前是否有 Safepoint 挂起请求,
// 如果有,当前线程会在此时主动进入 Safepoint 阻塞,直到 GC 结束。
}
四、 Parker::unpark() C++ 源码深挖
unpark() 的职责是设置许可并通知 pthread 条件变量。
// hotspot/src/os/linux/vm/os_linux.cpp
void Parker::unpark(){
int s;
int status;
// 1. 获取 pthread 互斥锁,保护 _counter 的修改
status = pthread_mutex_lock(_mutex);
assert (status == 0, "invariant");
s = _counter;
_counter = 1; // 无条件将 _counter 置为 1
if (s < 1) {
// 2. 说明先前 _counter == 0,目标线程可能处于 pthread_cond_wait 阻塞状态
// Linux/NPTL 平台死锁/Bug 绕过策略机制 (WorkAroundNPTLBug)
if (WorkAroundNPTLBug) {
// 策略 A:先发出唤醒信号,再释放互斥锁
status = pthread_cond_signal(_cond);
assert (status == 0, "invariant");
status = pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant");
} else {
// 策略 B:先释放互斥锁,再发出唤醒信号
// 避免唤醒后的线程在 pthread_cond_wait 内部重新竞争 _mutex 导致的线程上下文切换开销
status = pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant");
status = pthread_cond_signal(_cond);
assert (status == 0, "invariant");
}
} else {
// 3. 如果先前 _counter 本身就是 1,说明目标线程没有被阻塞,或者已经被 unpark 过,
// 直接释放锁即可,不用调用昂贵的 pthread_cond_signal
status = pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant");
}
}
五、 Linux pthread 库与 Kernel Futex 交互机制
HotSpot 的 Parker 实现直接构建在 Linux 的 pthread 原语之上,而现代 Linux 的 pthread_mutex 与 pthread_cond 底层机制是 futex(Fast Userspace Mutex)。
JVM (Parker::park)
└─> POSIX pthread (pthread_cond_wait)
└─> Glibc (NPTL)
└─> Linux Kernel System Call: sys_futex()
├─> Fast Path: 用户态 CAS (没有任何内核开销)
└─> Slow Path: 陷入内核态,把当前 Task 结构体加入 wait_queue,调用 schedule()
1. pthread_cond_wait 的原子解锁与挂起
当 Parker::park() 执行到 pthread_cond_wait(_cond, _mutex) 时,在 Glibc 与 Linux 内核层面会发生以下原子操作:
用户态/内核态原语: pthread_cond_wait将当前线程挂入条件变量的等待队列。释放 Mutex 锁:原子地释放 _mutex(底层触发futex(FUTEX_UNLOCK_PI)或对用户态锁标记清零)。内核态阻塞:调用 Linux 系统调用 sys_futex(addr, FUTEX_WAIT_PRIVATE, val, ...)。
Linux 内核将当前进程/线程的 task_struct状态设置为TASK_INTERRUPTIBLE。将当前线程节点挂载到与 addr(_cond的内存地址)绑定的内核 futex hash 桶的等待队列中。调用 schedule()放弃 CPU 时间片,触发内核上下文切换(Context Switch)。
2. pthread_cond_signal 的唤醒过程
当另一线程调用 Parker::unpark() 触发 pthread_cond_signal(_cond) 时:
执行系统调用 sys_futex(addr, FUTEX_WAKE_PRIVATE, 1, ...)。Linux 内核根据 _cond的内存地址计算 Hash,找到对应的 futex 等待队列。从队列头部弹出等待的 task_struct,将其状态由TASK_INTERRUPTIBLE修改为TASK_RUNNING。将线程重新加入操作系统的可运行调度队列(Runqueue),等待 OS 调度器挑选 CPU 执行。 被唤醒的线程在 pthread_cond_wait内部重新竞争获取_mutex,成功后从pthread_cond_wait返回,继续执行后续 C++ 代码。
3. 时钟选型:CLOCK_MONOTONIC vs CLOCK_REALTIME
为了防止 NTP 网络对时修改系统时间(System Clock)导致 LockSupport.parkNanos() 发生无限期挂起或意外提前唤醒,HotSpot 在 Linux 平台对时钟源进行了特殊配置:
// hotspot/src/os/linux/vm/os_linux.cpp 中的 PlatformParker 初始化
os::PlatformParker::PlatformParker() {
int status;
status = pthread_mutex_init(_mutex, NULL);
assert_status(status == 0, status, "mutex_init");
// _cond[0] 配置为 CLOCK_MONOTONIC (单调时钟)
// 保证 parkNanos 不受系统墙上时间(Wall Time)修改的影响
pthread_condattr_t attr;
pthread_condattr_init(&attr);
pthread_condattr_setclock(&attr, CLOCK_MONOTONIC);
status = pthread_cond_init(&_cond[0], &attr);
pthread_condattr_destroy(&attr);
// _cond[1] 使用默认的 CLOCK_REALTIME (系统绝对时间)
// 用于 parkUntil 等依赖绝对时间戳的挂起
status = pthread_cond_init(&_cond[1], NULL);
}
六、 许可机制 (_counter) 的物理运行状态图解
通过对 _counter 的操作逻辑分析,可以完美匹配 unpark 与 park 调用顺序乱序时的物理表现:
场景 A:先 park(),后 unpark()
Thread A (park) Thread B (unpark)
| |
_counter == 0 |
CAS 抢占失败 |
| |
获取 _mutex |
_counter 仍为 0 |
调用 pthread_cond_wait 陷入阻塞 ------> (Thread A 处于 TASK_INTERRUPTIBLE)
|
获取 _mutex
_counter = 1
调用 pthread_cond_signal
|
Thread A 被内核唤醒 <-----------------------+
竞争获取 _mutex
_counter 置为 0
释放 _mutex,退出 park()
场景 B:先 unpark(),后 park()(许可预置)
Thread B (unpark) Thread A (park)
| |
获取 _mutex |
_counter 设置为 1 |
释放 _mutex,退出 unpark() |
Atomic::xchg(0, &_counter)
发现 _counter == 1,将其原子置 0
【Fast-Path 直接返回】
(无需加锁,无需系统调用,零内核开销)
七、 HotSpot 线程同步原语纵向对比
在 OpenJDK C++ 源码层级,除了 Parker,还有 PlatformEvent 和 ObjectMonitor,它们构成了 JVM 不同的同步层次:
LockSupport | Parker | JavaThread (thread->parker()) | volatile int _counter | java.util.concurrent (AQS) 在用户态构建复杂的同步器。 |
synchronized | ObjectMonitor | _owner_WaitSet, _EntryList | ||
| JVM 内部锁 | PlatformEvent | Thread (C++ 内核线程) | volatile int _Event | Mutex / Monitor 内部线程同步、GC 线程同步),不暴露给 Java 语言层。 |
夜雨聆风