reentrantlock 的可重入性通过 aqs 的 state 变量实现:state 既是锁状态标识也是重入计数器,首次加锁设为1,重入时递增,解锁时递减,仅当 state 归零才真正释放锁。

state 变量如何记录同一线程的多次加锁
ReentrantLock 的可重入性不靠线程 ID 映射表或额外字段,而是复用 AQS 的 state 这个 volatile int 变量——它既是锁状态标识,也是重入计数器。当线程第一次调用 lock() 且成功时,state 从 0 变为 1;再次调用 lock()(仍在同一栈帧或递归调用中),state 变为 2;依此类推。
关键点在于:AQS 的 tryAcquire() 实现(由 NonfairSync 或 FairSync 提供)会先检查当前持有锁的线程是否是当前线程,如果是,就跳过 CAS 竞争,直接执行 setState(state + 1) ——这个操作不依赖 CAS,因为此时线程已持锁,无并发修改风险。
-
state == 0:无人持锁,走正常 CAS 获取路径 -
state > 0 && getExclusiveOwnerThread() == currentThread:当前线程已持锁,仅递增state -
state > 0 && getExclusiveOwnerThread() != currentThread:其他线程持锁,当前线程入等待队列
为什么 state 递增不需 CAS 就安全
因为递增只发生在「当前线程已确定是锁持有者」的前提下,该判断本身由 AQS 的 getExclusiveOwnerThread() 完成,而这个值是在上一次成功获取锁时通过 setExclusiveOwnerThread(current) 写入的。一旦进入重入分支,整个操作处于单线程上下文:没有其他线程能同时修改该线程专属的 state 增量,所以直接 setState(state + 1) 是线程安全的,无需原子指令。
这和首次获取锁时必须用 CAS 更新 state 和 owner 形成鲜明对比——后者是多线程竞争临界点,前者只是单线程内部计数。
- 首次加锁:需原子更新
state和 owner,失败则排队 - 重入加锁:owner 已确认为当前线程,
state递增是纯本地操作 - 解锁时:每次
unlock()都执行setState(state - 1),仅当state == 0才真正释放并唤醒后继节点
state 归零才是真正的锁释放
很多人误以为调一次 unlock() 就等于释放了锁,其实不然。unlock() 只是将 state 减 1;只有减到 0 时,AQS 才会清理 owner、唤醒等待队列首节点,并允许下一个线程尝试获取锁。这意味着:
- 若一个线程调用了 3 次
lock(),必须严格匹配 3 次unlock(),否则锁仍被持有 - 在 try-finally 中漏写一次
unlock(),会导致state卡在非零值,后续所有线程永久阻塞 - 调试时可通过
getHoldCount()查看当前线程的state值,比打印日志更直接反映重入深度
公平锁与非公平锁对 state 的使用完全一致
公平性只影响「新线程能否插队」,不影响 state 的语义和更新逻辑。无论是 FairSync 还是 NonfairSync,它们的 tryAcquire() 在识别到当前线程已持锁时,都走相同的重入递增路径;释放时也都依赖 state 归零才触发唤醒。区别仅在于:非公平锁在首次尝试时会绕过队列直接 CAS,而公平锁会先检查队列是否为空,再决定是否竞争。
换句话说,可重入性是 AQS 同步器层面的通用能力,不是 ReentrantLock 的特有补丁;只要子类在 tryAcquire() 中实现了 owner 判断 + state 递增,就天然支持可重入——这也是为什么自定义同步器也能轻松实现可重入语义。
真正容易忽略的是:state 的值永远只对当前持有线程有意义,其他线程看到的只是一个数字;它的正确性完全依赖于 AQS 对 owner 字段的严格维护和线程局部的调用顺序。一旦跨线程误传 lock/unlock 调用,或者在未加锁状态下调用 unlock,state 就会失真,且这种错误往往不会立刻抛异常,而是静默导致死锁或并发错乱。










