LockSupport 与 Thread 许可(permit)完整对应原理

一、核心本质:每个 Thread 持有独立许可(permit)

  1. 许可不是锁,是0/1计数器
    • 每个 Thread 对象内部维护一个 parkBlocker + 许可计数器(int permit)
    • permit 取值只能是 0 或 1,不会累积超过1
    • 许可属于线程本身,不属于 LockSupport、不属于锁对象
  2. 核心两个操作,成对操作许可:
    • 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)

  1. 如果 t.permit == 0 → 置为 1(发放1份许可)
  2. 如果 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())

  1. 读取当前线程permit
    • permit = 1:直接置为0,方法立刻返回,不阻塞
    • permit = 0:线程阻塞,等待其他线程调用 unpark(当前线程) 发放许可
  2. 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立刻唤醒

四、线程与许可一一绑定的关键特性

  1. 许可线程隔离
    A 线程 unpark(A) 只会修改 A 的许可,完全不影响 B、C 线程;
    B 调用 park() 只消耗 B 自己的许可,和A无关。
  2. unpark 必须精准指定线程引用
    想要唤醒某个线程,必须持有该线程的 Thread 实例,不能通过锁/对象唤醒(和 Object.wait/notify 本质区别)
    • Object.notify:基于对象监视器,随机唤醒一个等待该对象的线程
    • LockSupport.unpark:精准定向某一个线程,依靠 Thread 引用绑定许可
  3. 许可生命周期跟随线程
    线程销毁后,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 引用):

  1. 线程入队后执行 LockSupport.park(),消耗自身许可阻塞;
  2. 线程释放锁时,拿到队列下一个 Node 的 Thread t,调用 LockSupport.unpark(t)
  3. unpark 精准给下一个等待线程发放私有许可,唤醒指定线程;
    完全依靠「Thread 独立许可」实现精准唤醒,而不是 Object 监视器的随机唤醒。

总结一句话对应关系

每一个 Thread 对象在 JVM 底层都有独一份的 0/1 许可计数器;

  • unpark(目标线程):修改目标线程自身的许可为1;
  • park():读取并消耗当前执行线程自身的许可,无许可则阻塞;
    许可严格和线程实例一一绑定,线程之间许可完全隔离、互不干扰。
posted @ 2026-07-24 12:50  七星6609  阅读(14)  评论(0)    收藏  举报