
reentrantreadwritelock通过锁的获取与释放建立happens-before关系,确保写操作对后续获取同一锁的读/写线程可见;但仅靠锁本身无法强制线程执行顺序,必须配合正确逻辑控制才能避免数据竞争。
reentrantreadwritelock通过锁的获取与释放建立happens-before关系,确保写操作对后续获取同一锁的读/写线程可见;但仅靠锁本身无法强制线程执行顺序,必须配合正确逻辑控制才能避免数据竞争。
ReentrantReadWriteLock 是 Java 并发包中提供的重要同步工具,它支持分离的读锁与写锁,允许多个读线程并发访问,但写操作独占。其与 Java 内存模型(JMM)的交互核心在于:锁的获取与释放操作构成 JMM 定义的“synchronization actions”,从而建立 happens-before 关系。
根据 JMM 规范,当线程 T1 释放一个锁(如 writeLock.unlock()),而线程 T2 随后成功获取该同一锁(如 writeLock.lock()),则 T1 在释放锁前的所有内存写操作,对 T2 在获取锁后的所有读操作 happens-before —— 这意味着这些写操作的结果对 T2 是保证可见的。
以你描述的场景为例:
// 假设 sharedValue 是普通 long 字段(非 volatile)
private long sharedValue = 0;
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock writeLock = rwLock.writeLock();
// Thread A
writeLock.lock();
try {
sharedValue++; // 非 volatile 写入
} finally {
writeLock.unlock(); // 释放写锁 → 向 JMM 发出“写屏障”
}
// Thread B(后续执行)
writeLock.lock(); // 获取写锁 → 向 JMM 发出“读屏障”
try {
// 此处 guaranteed to see the updated sharedValue
System.out.println(sharedValue); // ✅ 正确看到 Thread A 的修改
} finally {
writeLock.unlock();
}
✅ 关键点:可见性保障的前提是“锁的释放-获取链”成立,即 T2 的 lock() 必须在 T1 的 unlock() 之后发生(且无其他线程干扰该锁状态)。这依赖于锁本身的互斥语义,而非时间先后——JMM 保证的是 synchronized order,不是 wall-clock 顺序。
⚠️ 但请注意:
-
ReentrantReadWriteLock不保证线程执行顺序。仅用一个锁无法强制“Thread A 必须先于 Thread B 执行”。若实际调度为 B 先抢到锁、A 后执行,或二者并发访问未受保护的代码路径,则仍存在数据竞争。 - 若 Thread B 以读锁(
readLock().lock())读取sharedValue,而 Thread A 以写锁更新,只要读锁与写锁是同一ReentrantReadWriteLock实例,JMM 依然通过锁的 release/acquire 边界提供 happens-before 保证(写锁释放 → 读锁获取 → 读可见)。 - 若
sharedValue被多个线程在无锁保护下直接读写(例如某处遗漏了 lock/unlock),则无论锁如何使用,该字段都处于竞态状态,JMM 不提供任何保证。
? 总结建议:
-
永远成对使用
lock()/unlock()(推荐 try-finally 或 try-with-resources),避免锁泄漏破坏同步边界; - 确认所有对共享变量的访问均受同一锁保护——不能一部分用锁,一部分裸读写;
- 若需更细粒度控制或跨锁协调,考虑结合
volatile字段、AtomicLong或显式VarHandle内存屏障; - 调试时可借助 JMH、jcstress 或
Unsafe内存模型测试工具验证可见性假设,而非依赖日志时序推断。
锁是构建 happens-before 关系的可靠手段,但它不是“魔法时序控制器”——正确性最终取决于你如何用锁围住临界区,并确保所有相关访问都被覆盖。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











