ReentrantReadWriteLock 的读锁重入通过 ThreadLocalHoldCounter 和 HoldCounter 实现,而非普通 ThreadLocal<Integer>,以节省内存、避免频繁对象创建,并与 AQS state 高 16 位协同记录总持有数;HoldCounter 封装 count 和 tid 用于校验与清理,且弱引用设计支持 GC 回收,不匹配时自动重建。

Java 中 ReentrantReadWriteLock 的读锁确实支持重入,但它的重入计数机制与写锁不同:读锁不是靠每个线程独立的重入计数器(如写锁用的 ThreadLocal<Integer>),而是通过一个共享的、线程局部的 HoldCounter 缓存结构——即 ThreadLocalHoldCounter ——来记录当前线程的读锁持有次数。
为什么读锁不用普通 ThreadLocal<Integer>?
直接用 ThreadLocal<Integer> 理论上可行,但 ReentrantReadWriteLock 选择自定义 HoldCounter 类并配合 ThreadLocalHoldCounter,主要出于两点考虑:
-
节省内存与避免频繁创建对象:
HoldCounter是轻量对象,复用比每次 new Integer 更高效; -
与同步状态(state)协同设计:读锁的总持有数需反映在 AQS 的
state高 16 位中,而每个线程的局部计数需可查、可减、可清零,HoldCounter封装了count和tid(线程 ID),便于校验和清理。
ThreadLocalHoldCounter 是什么?
ThreadLocalHoldCounter 是 ReentrantReadWriteLock 的一个内部类,继承自 ThreadLocal<HoldCounter>。它重写了 initialValue() 方法,返回一个 HoldCounter 实例,其中 tid 被设为当前线程 ID。
关键点:
立即学习“Java免费学习笔记(深入)”;
- 每个线程首次访问读锁时,会获得专属的
HoldCounter,存于该线程的ThreadLocal中; -
HoldCounter.count记录该线程当前持有的读锁次数(即重入深度); -
HoldCounter.tid用于防御性校验(防止被其他线程篡改或误用)。
读锁重入时如何更新计数?
调用 readLock().lock() 或 tryLock() 时,核心流程包括:
- 从
readHolds(即ThreadLocalHoldCounter实例)获取当前线程的HoldCounter; - 若
count == 0,说明是首次获取读锁,先尝试 CAS 更新 AQS state(高 16 位 +1),成功后将count设为 1; - 若
count > 0,说明已持有,直接递增count,不再修改 state(因为 state 高位已体现总持有数); - 释放时同理:只减
count,仅当减为 0 才真正释放(CAS 减 state 高 16 位)。
注意:HoldCounter 不是强引用,可能被回收
ThreadLocalHoldCounter 使用的是弱引用键的 ThreadLocal(JDK 默认行为),因此若线程长时间不操作读锁,其 HoldCounter 可能被 GC。但框架做了兜底:
当再次进入读锁逻辑时,会检查 HoldCounter.tid 是否匹配当前线程 ID;不匹配则重建一个新的 HoldCounter,并重新初始化 count。
这也意味着:不能依赖 ThreadLocal 中的 HoldCounter 长期存活,也不应手动修改或缓存它。


















