乐于分享
好东西不私藏

JUC包LockSupport源码剖析

JUC包LockSupport源码剖析
  • 一、 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(voidUnsafe_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(truetrue)) {
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(truetrue)) {
// 再次确认中断标志
  }
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) > 0return;

  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 内核层面会发生以下原子操作:

  1. 用户态/内核态原语pthread_cond_wait 将当前线程挂入条件变量的等待队列。
  2. 释放 Mutex 锁:原子地释放 _mutex(底层触发 futex(FUTEX_UNLOCK_PI) 或对用户态锁标记清零)。
  3. 内核态阻塞:调用 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) 时:

  1. 执行系统调用 sys_futex(addr, FUTEX_WAKE_PRIVATE, 1, ...)
  2. Linux 内核根据 _cond 的内存地址计算 Hash,找到对应的 futex 等待队列。
  3. 从队列头部弹出等待的 task_struct,将其状态由 TASK_INTERRUPTIBLE 修改为 TASK_RUNNING
  4. 将线程重新加入操作系统的可运行调度队列(Runqueue),等待 OS 调度器挑选 CPU 执行。
  5. 被唤醒的线程在 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 不同的同步层次:

同步组件
C++ 实现类
关联的目标
核心控制状态
设计目的
LockSupportParker
绑定到 JavaThread (thread->parker())
volatile int _counter
 (0 或 1)
供 java.util.concurrent (AQS) 在用户态构建复杂的同步器。
synchronizedObjectMonitor
动态绑定到 Java 对象头 (Mark Word)
_owner
_WaitSet_EntryList
实现 Java 语言原生的内置锁机制,处理重量级锁竞争。
JVM 内部锁PlatformEvent
绑定到 Thread (C++ 内核线程)
volatile int _Event
仅供 JVM 内核自身使用(如 Mutex / Monitor 内部线程同步、GC 线程同步),不暴露给 Java 语言层。