bigywater.tech
Java · 143

AQS:ReentrantLock 是怎么排队和唤醒线程的

ReentrantLock 怎么让线程排队、怎么唤醒下一个?这篇不看源码讲解,直接用反射读 AQS 的 state 和队列节点,再用公平锁与非公平锁做对照(两种锁的耗时差别已有专门的文章)。

0 → 1 → 2
state 随加锁与重入的变化
哨兵 → T1 → T2 → T3
3 个等待线程在队列里的样子
31 / 7 / 1
非公平 tryLock() / 公平 tryLock() / 公平 tryLock(0,SECONDS) 的插队次数

state 与队列

实测 · 本机执行输出
  初始 state = 0,队列 =
  main 加锁一次后 state = 1
  main 重入第二次后 state = 2
  3 个线程依次排队后(每个间隔 80ms):
  队列 = [哨兵 status=0] → [T1 status=1] → [T2 status=1] → [T3 status=1]
  getQueueLength() = 3;T1 的状态 = WAITING
  T1 的栈顶:Unsafe.park ← LockSupport.park ← AbstractQueuedSynchronizer.acquire ← AbstractQueuedSynchronizer.acquire
  main 第一次 unlock 后 state = 1(还持有一次,队列里的线程不会被唤醒)
  T1 仍然是 WAITING
  main 完全释放后,三个线程获得锁的顺序 = [T1, T2, T3]
  全部结束后 state = 0,队列 = [哨兵 status=0]

反射读内部字段需要 --add-opens java.base/java.util.concurrent.locks=ALL-UNNAMED;字段名(state、head、waiter、status)是 JDK 21 的实现细节。

排队顺序

实测 · 本机执行输出
  非公平锁:4 个线程依次排队,释放后按排队顺序拿到锁的轮数 = 30 / 30
  公平锁:4 个线程依次排队,释放后按排队顺序拿到锁的轮数 = 30 / 30

插队

实测 · 本机执行输出
  非公平锁 tryLock():unlock 之后立刻再拿,成功(插队) 31 次
  公平锁 tryLock():unlock 之后立刻再拿,成功(插队) 7 次
  公平锁 tryLock(0, SECONDS):unlock 之后立刻再拿,成功(插队) 1 次

分步演示

AQS 队列
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。本篇用 JDK 21.0.12
    • 没有验证:Condition 的等待队列与 signal 的转移、共享模式(Semaphore、CountDownLatch)的传播唤醒、取消节点的清理、JDK 17 与 27 里节点字段的差异;插队次数是一次运行的结果,每次不同
    京ICP备2026067502号-1京公网安备11010502063487号