
reentrantreadwritelock通过锁的获取与释放建立happens-before关系,确保临界区内的内存操作对后续获取同一锁的线程可见;但线程执行顺序本身不被锁保证,需显式同步逻辑或外部调度约束。
reentrantreadwritelock通过锁的获取与释放建立happens-before关系,确保临界区内的内存操作对后续获取同一锁的线程可见;但线程执行顺序本身不被锁保证,需显式同步逻辑或外部调度约束。
ReentrantReadWriteLock 是 Java 并发包中提供读写分离语义的可重入锁,其核心价值不仅在于提升并发吞吐量,更在于它严格遵循 Java 内存模型(JMM)的同步规则——即通过锁的 acquire/release 操作建立 happens-before 关系,从而保障共享变量的可见性与有序性。
锁操作构建 happens-before 链
根据 JMM 规范,成功获取锁(lock())与之前该锁的最后一次释放(unlock())之间构成 happens-before 关系。这意味着:
- 若线程 A 调用
writeLock.unlock(), - 线程 B 随后调用
writeLock.lock()成功返回,
→ 则 A 在 unlock 前的所有内存写操作(包括对非 volatilelong变量的修改),对 B 在 lock 后的读操作一定可见。
这正是你场景中第 1–6 步能成立的前提:前提是“线程 B 确实在线程 A 释放锁之后才获取到锁”这一顺序被程序逻辑所保证。
// ✅ 正确示例:依赖锁的 happens-before 保证可见性
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private long counter = 0;
// Thread A
rwLock.writeLock().lock();
try {
counter++; // 非 volatile,但受锁保护
} finally {
rwLock.writeLock().unlock(); // 释放 → 建立 hb 边界
}
// Thread B(必须在 A unlock 后才能 lock 成功)
rwLock.writeLock().lock();
try {
long val = counter; // ✅ 一定能看到 A 的更新值
} finally {
rwLock.writeLock().unlock();
}⚠️ 关键警告:锁不保证执行时序!
然而,ReentrantReadWriteLock 本身不控制线程调度顺序。仅靠一个锁无法强制“Thread A 必须先执行完再轮到 Thread B”。以下竞态行为完全合法且可能真实发生:
立即学习“Java免费学习笔记(深入)”;
- Thread B 先抢到写锁并完成
unlock(); - Thread A 后获得锁、修改
counter、再释放; - Thread B 此时若再次读取(未加锁),就可能看到旧值 —— 因为这次读不在同步块内,也不满足任何 happens-before 条件。
更危险的是:若 Thread B 在步骤 6 中的读操作未受任何锁保护(如直接访问裸 counter),则无论之前是否有锁交互,该读都无 JMM 保证,结果不可预测。
✅ 正确实践建议
-
所有共享变量访问必须受锁保护:读/写均需对应获取读锁或写锁(
readLock().lock()/writeLock().lock()),避免裸读裸写; - 避免混合访问模式:不要对同一变量有时加锁、有时不加锁;
-
慎用“隐式顺序假设”:不能仅因“我期望 A 先跑”就省略显式同步机制(如
CountDownLatch、CyclicBarrier或状态标志 + 循环检查); -
考虑 volatile 的适用边界:若仅需单次写+多线程可见(无复合操作),
volatile long更轻量;但涉及counter++这类读-改-写,则必须用锁或AtomicLong。
总之,ReentrantReadWriteLock 是 JMM 的忠实执行者,它提供的不是“魔法时序”,而是可验证的内存同步契约。是否出现 stale value,最终取决于你的代码是否真正建立了完整的 happens-before 链 —— 而这,永远由你编写的同步结构决定,而非线程运气。


















