LockSupport 与 Thread 许可(permit)完整对应原理
一、核心本质:每个 Thread 持有独立许可(permit)
- 许可不是锁,是0/1计数器
- 每个
Thread对象内部维护一个parkBlocker+ 许可计数器(int permit) - permit 取值只能是 0 或 1,不会累积超过1
- 许可属于线程本身,不属于 LockSupport、不属于锁对象
- 每个
- 核心两个操作,成对操作许可:
LockSupport.park():消费1份许可;无许可则阻塞当前线程LockSupport.unpark(Thread t):给目标线程 t 发放1份许可;已有许可则无变化(不会叠加)
二、底层存储:许可存在哪里?HotSpot 源码对应
1. Java 层封装(sun.misc.Unsafe)
LockSupport 全部方法基于 Unsafe:
public static void park() {
Unsafe.park(false, 0L);
}
public static void unpark(Thread thread) {
if (thread != null)
Unsafe.unpark(thread);
}
2. JVM 底层 C++ 实现(HotSpot thread.hpp)
每个 JavaThread 结构体有字段:
class JavaThread : public Thread {
private:
// 许可标记:0=无许可,1=有许可
int _park_counter;
// 阻塞对象,用于监控工具
oop _park_blocker;
};
_park_counter= 就是所谓的permit 许可,线程私有,一一对应- 每一个 Thread 独立一份 counter,线程之间互不干扰
三、park / unpark 对许可的修改规则(一一对应线程)
设目标线程为 t,初始状态 permit = 0
规则1:unpark(t) 发放许可(针对指定线程t)
- 如果
t.permit == 0→ 置为1(发放1份许可) - 如果
t.permit == 1→ 不变,依旧是1(许可不可叠加)
重点:unpark 必须传入指定 Thread 对象,只修改该线程自己的 permit,不会影响其他线程
示例:
Thread a = new Thread();
LockSupport.unpark(a); // a.permit = 1
LockSupport.unpark(a); // a.permit 仍然=1,不会变成2
规则2:park() 消耗当前线程自身许可(只操作当前执行线程 Thread.currentThread())
- 读取当前线程的
permit:- permit = 1:直接置为0,方法立刻返回,不阻塞
- permit = 0:线程阻塞,等待其他线程调用
unpark(当前线程)发放许可
- park 只会操作自己线程的许可,无法操作别的线程
示例时序1:先 unpark,后 park(不会阻塞)
Thread t = Thread.currentThread();
LockSupport.unpark(t); // permit=1
LockSupport.park(); // 消耗许可 → permit=0,直接放行,无阻塞
时序2:先 park,后 unpark(阻塞唤醒)
// 线程A执行park,permit=0,阻塞
LockSupport.park();
// 线程B执行,传入线程A对象,给A发放许可
LockSupport.unpark(threadA); // A.permit=1,A立刻唤醒
四、线程与许可一一绑定的关键特性
- 许可线程隔离
A 线程 unpark(A) 只会修改 A 的许可,完全不影响 B、C 线程;
B 调用 park() 只消耗 B 自己的许可,和A无关。 - unpark 必须精准指定线程引用
想要唤醒某个线程,必须持有该线程的 Thread 实例,不能通过锁/对象唤醒(和 Object.wait/notify 本质区别)- Object.notify:基于对象监视器,随机唤醒一个等待该对象的线程
- LockSupport.unpark:精准定向某一个线程,依靠 Thread 引用绑定许可
- 许可生命周期跟随线程
线程销毁后,permit 计数器自动销毁,不存在跨线程传递许可。
五、对比 Object 监视器,理解「线程绑定许可」差异
| 特性 | Object wait/notify | LockSupport park/unpark |
|---|---|---|
| 等待凭证归属 | 归属对象监视器,多线程共享 | 归属单个Thread私有permit,线程一一对应 |
| 唤醒目标 | 随机唤醒等待该对象的线程 | 指定 Thread,精准定向唤醒单个线程 |
| 顺序要求 | 必须先wait后notify,颠倒失效 | unpark可提前发放许可,后park不阻塞 |
| 凭证数量 | 无计数,单次notify只能唤醒1个 | permit只有0/1,不可累积 |
六、经典易错点(线程许可绑定导致)
错误1:unpark 传错线程,无法唤醒
Thread t1 = new Thread(() -> LockSupport.park());
t1.start();
LockSupport.unpark(Thread.currentThread()); // 只给主线程发许可,t1永远阻塞
原因:许可绑定线程,unpark 参数是主线程,没有修改 t1 的 permit。
错误2:多轮 unpark 无效,许可不会叠加
LockSupport.unpark(Thread.currentThread()); // permit=1
LockSupport.unpark(Thread.currentThread()); // permit保持1
LockSupport.park(); // permit=0
LockSupport.park(); // permit=0,线程阻塞!
两次unpark只生成1份许可,只能放行一次park。
七、AQS 中如何利用「Thread私有许可」实现阻塞队列
AQS 内部 CLH 队列存储等待线程 Node(持有 Thread 引用):
- 线程入队后执行
LockSupport.park(),消耗自身许可阻塞; - 线程释放锁时,拿到队列下一个 Node 的
Thread t,调用LockSupport.unpark(t); - unpark 精准给下一个等待线程发放私有许可,唤醒指定线程;
完全依靠「Thread 独立许可」实现精准唤醒,而不是 Object 监视器的随机唤醒。
总结一句话对应关系
每一个 Thread 对象在 JVM 底层都有独一份的 0/1 许可计数器;
unpark(目标线程):修改目标线程自身的许可为1;park():读取并消耗当前执行线程自身的许可,无许可则阻塞;
许可严格和线程实例一一绑定,线程之间许可完全隔离、互不干扰。
百流积聚,江河是也;文若化风,可以砾石。

浙公网安备 33010602011771号