欢迎光临
我们一直在努力

Java 锁机制深度解析(系列四):读写锁与 Stamped...

Java 锁机制深度解析(系列四):读写锁与 StampedLock 深度解析

一、引言

在前三篇中,我们深入剖析了 synchronized的锁升级机制和 ReentrantLock的 AQS 实现。无论是 synchronized还是 ReentrantLock,它们本质上都是互斥锁——同一时刻只允许一个线程访问临界区。

但在现实场景中,有一种非常常见的访问模式:读多写少。例如缓存系统、配置中心、数据快照——大量线程需要并发读取数据,只有少量线程偶尔更新数据。如果用互斥锁,所有读线程之间也会互相阻塞,白白浪费了并发能力。

为了解决这个问题,JDK 提供了两种专门的锁:

锁引入版本特点
ReentrantReadWriteLock JDK 5 读写分离,读读不互斥,读写/写写互斥
StampedLock JDK 8 读写分离 + 乐观读,进一步提升读性能


二、ReentrantReadWriteLock 深度解析

2.1 设计思想

读写锁的核心思想是:读读不互斥,读写互斥,写写互斥。

在这里插入图片描述

2.2 类结构

public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
private final ReentrantReadWriteLock.ReadLock readerLock;
private final ReentrantReadWriteLock.WriteLock writerLock;
final Sync sync;

// 内部抽象类,继承 AQS
abstract static class Sync extends AbstractQueuedSynchronizer { ... }

// 公平/非公平实现
static final class NonfairSync extends Sync { ... }
static final class FairSync extends Sync { ... }

// 读锁实现
public static class ReadLock implements Lock { ... }

// 写锁实现
public static class WriteLock implements Lock { ... }
}

2.3 核心:如何用一个 int 存储两种状态?

这是 ReentrantReadWriteLock最精妙的设计——将 AQS 的 state字段拆分为两部分:

// Sync.java
static final int SHARED_SHIFT = 16; // 位移量
static final int SHARED_UNIT = (1 << SHARED_SHIFT); // 0x00010000
static final int MAX_COUNT = (1 << SHARED_SHIFT) 1; // 65535
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) 1; // 0x0000FFFF

// 从 state 中提取读锁计数
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
// 从 state 中提取写锁计数(重入次数)
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

state 布局(32 位 int):

在这里插入图片描述

为什么读锁计数要占用高 16 位?

因为读锁是共享锁,可能有多个线程同时持有;写锁是独占锁,最多只有一个线程持有(可重入)。高 16 位可以表示 65535 个并发读线程,低 16 位表示写锁 65535 次重入,均足够使用。

2.4 写锁获取:WriteLock.lock()

// WriteLock.java
public void lock() {
sync.acquire(1); // 调用 AQS 的独占模式获取
}

Sync.tryAcquire() —— 写锁获取的核心逻辑:

// Sync.java
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c); // 写锁重入计数

if (c != 0) { // 已有线程持有锁(读或写)
// 情况1:有读锁持有(sharedCount(c) != 0)→ 不能获取写锁
// 情况2:写锁被其他线程持有 → 不能获取
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 当前线程已持有写锁 → 重入
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
setState(c + acquires); // 重入:state + 1
return true;
}

// c == 0:锁空闲
// 非公平锁:writerShouldBlock() 永远返回 false
// 公平锁:检查队列中是否有更早的等待者
if (!writerShouldBlock() &&
compareAndSetState(c, c + acquires)) {
setExclusiveOwnerThread(current);
return true;
}
return false;
}

关键逻辑总结:

锁状态 (c) 能否获取写锁?
───────────────────────────────────
c == 0 → 可以(CAS 抢锁)
c != 0, 有读锁 → 不可以(读锁阻塞写锁)
c != 0, 有写锁(自身) → 可以(重入)
c != 0, 有写锁(其他) → 不可以

2.5 读锁获取:ReadLock.lock()

// ReadLock.java
public void lock() {
sync.acquireShared(1); // 调用 AQS 的共享模式获取
}

Sync.tryAcquireShared() —— 读锁获取的核心逻辑:

// Sync.java
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();

// ⭐ 写锁被其他线程持有 → 读锁必须等待(读写互斥)
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return 1; // 失败,进入等待队列

// 执行到这里:要么锁空闲,要么当前线程已持有写锁(锁降级)
int r = sharedCount(c); // 当前读锁持有数

// 非公平锁:readerShouldBlock() 检查队列头部是否为写锁等待者
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) { // state + 65536
// CAS 成功,获取读锁
if (r == 0) {
firstReader = current; // 第一个读线程
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++; // 重入
} else {
// 使用 ThreadLocal 记录每个线程的重入次数
HoldCounter rh = cachedHoldCounter;
// … 更新 ThreadLocal 中的计数
}
return 1; // 成功
}

// CAS 失败(竞争激烈),走完整版获取
return fullTryAcquireShared(current);
}

fullTryAcquireShared() —— 完整的读锁获取(自旋重试):

// Sync.java
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) {
int c = getState();

if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return 1; // 写锁被其他线程持有,排队
// 当前线程持有写锁 → 可以降级获取读锁
} else if (readerShouldBlock()) {
// 非公平锁:队列头部有写锁在等待 → 读锁应该让步
// 但如果当前线程已经持有读锁,可以继续获取(避免死锁)
if (firstReader == current) {
// 第一个读线程,不需要检查
} else {
if (rh == null)
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
if (rh.count == 0) // 当前线程未持有读锁
return 1; // 让写锁先执行
}
}

if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");

if (compareAndSetState(c, c + SHARED_UNIT)) {
// CAS 成功
// … 更新 firstReader / HoldCounter
return 1;
}
// CAS 失败 → 自旋重试
}
}

2.6 锁降级(Lock Downgrade)

概念:持有写锁的线程可以获取读锁,然后释放写锁,从而将写锁降级为读锁。

在这里插入图片描述

class CacheExample {
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private final Lock readLock = rwl.readLock();
private final Lock writeLock = rwl.writeLock();

private volatile boolean cacheValid;
private Object cachedData;

public void processData() {
readLock.lock();
if (!cacheValid) {
// 发现缓存失效,必须先释放读锁再获取写锁
readLock.unlock();
writeLock.lock();
try {
// 双重检查:其他线程可能已经更新了缓存
if (!cacheValid) {
cachedData = loadFromDatabase(); // 更新缓存
cacheValid = true;
}
// ⭐ 锁降级:在释放写锁之前获取读锁
readLock.lock();
} finally {
writeLock.unlock(); // 写锁释放,降级完成
}
}

try {
useData(cachedData); // 此时持有读锁
} finally {
readLock.unlock();
}
}
}

为什么需要锁降级?

如果不降级,释放写锁后立即获取读锁,中间有一个无锁窗口期——此时其他写线程可以进入并修改数据。通过"先获取读锁再释放写锁",保证了从写操作到读操作的平滑过渡,数据一致性得到保障。

2.7 写锁等待的公平性问题(非公平模式下的读锁插队)

非公平模式下,readerShouldBlock() 的实现决定了读锁是否会"插队":

// NonfairSync.java
final boolean readerShouldBlock() {
return apparentlyFirstQueuedIsExclusive();
// 检查队列中第一个等待者是否是写锁
// 如果是 → 读锁应该阻塞,防止写锁饥饿
// 如果不是 → 读锁可以插队
}

// AQS.java
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() && // 第一个等待者是独占模式(写锁)
s.thread != null;
}

设计意图:如果等待队列头部有一个写锁在等待,后续的读锁就不能插队,否则写锁可能永远被读锁阻塞(写锁饥饿)。

时间线(非公平锁):

t0: 读锁A 持有中
t1: 写锁B 尝试获取 → 失败,进入等待队列
t2: 读锁C 尝试获取 → apparentlyFirstQueuedIsExclusive() 返回 true
→ 读锁C 不会插队,也进入等待队列
→ 写锁B 前面没有新的读锁插入,最终能获取到锁


三、StampedLock 深度解析

3.1 为什么还需要 StampedLock?

ReentrantReadWriteLock 虽然实现了读写分离,但读锁之间仍然有 CAS 竞争(多个读线程 CAS 修改 state 的高 16 位)。在高并发读场景下,这种 CAS 竞争也会成为瓶颈。

StampedLock 引入了一种乐观读(Optimistic Read) 机制——乐观读完全不获取锁,不加任何 CAS 操作,只在读取后通过 validate()检查是否有写操作发生。

3.2 三种访问模式

StampedLock 提供了三种锁模式:

模式方法说明
写锁 writeLock() / tryWriteLock() 独占模式,与所有模式互斥
悲观读锁 readLock() / tryReadLock() 共享模式,读读不互斥(类似读写锁的读锁)
乐观读 tryOptimisticRead() 不加锁,通过版本号校验,失败可升级为悲观读

3.3 核心原理:版本号(stamp)

StampedLock 内部使用一个 long 类型的 stamp(版本戳)来控制并发访问:

public class StampedLock implements java.io.Serializable {
// stamp 的高位表示写锁版本,最低位表示写锁是否被持有
private volatile long state; // 初始值 0

private static final long RUNIT = 1L; // 读锁单位
private static final long WBIT = 1L << 7; // 写锁标志位 (128)
private static final long RBITS = WBIT 1L; // 读锁计数掩码 (127)
private static final long RFULL = RBITS 1L; // 满读锁计数 (126)
private static final long ABITS = RBITS | WBIT; // 所有状态位
private static final long SBITS = ~RBITS; // 版本号掩码
}

state 布局:

bit 63-8 bit 7 bit 6-0
┌─────────────────────┬──────────┬──────────────┐
│ 版本号(读 stamp) │ 写锁标志 │ 读锁计数 │
│ (56 bits) │ 1 bit │ 7 bits │
└─────────────────────┴──────────┴──────────────┘

写锁被持有时,bit 7 = 1,版本号 + 1
每获取一次读锁,读锁计数 + 1(bit 6-0)

stamp 的含义:

// 写锁返回的 stamp
long stamp = lock.writeLock(); // stamp = 0x0000 0000 0000 0080
// bit 7 = 1,表示写锁标志位置位

// 读锁返回的 stamp
long stamp = lock.readLock(); // stamp = state 的快照值

// 乐观读返回的 stamp
long stamp = lock.tryOptimisticRead(); // stamp = state 的低7位清0后的值
// 本质是当前版本号

3.4 写锁获取:writeLock()

// StampedLock.java
public long writeLock() {
long s, next; // next = state + WBIT,将写锁标志位置1
return ((((s = state) & ABITS) == 0L && // 无锁(无读锁无写锁)
U.compareAndSwapLong(this, STATE, s, next = s + WBIT)) ?
next : acquireWrite(false, 0L));
// CAS 成功 → 返回 stamp
// CAS 失败 → 进入 acquireWrite() 自旋/阻塞
}

条件检查:(state & ABITS) == 0L 确保没有读锁也没有写锁,因为:

  • ABITS = 0x00FF(低 8 位全部为 1)
  • 如果有读锁或写锁,低 8 位必定不为 0

acquireWrite() —— 写锁获取失败后的处理:

// StampedLock.java (简化)
private long acquireWrite(boolean interruptible, long deadline) {
Node node = null;
boolean parked = false;
for (int spins = 1;;) {
long s, ns;

// 阶段1:自旋尝试
if (((s = state) & ABITS) == 0L) { // 锁空闲
if (U.compareAndSwapLong(this, STATE, s, ns = s + WBIT))
return ns; // 自旋成功
}

// 阶段2:自旋一定次数后,创建节点
// 阶段3:入队
// 阶段4:阻塞(park)
}
}

3.5 悲观读锁:readLock()

// StampedLock.java
public long readLock() {
long s = state, next;
// 如果写锁未被持有 (s & WBIT == 0) 且读锁未满 (s & RBITS < RFULL)
return ((s & WBIT) == 0L &&
(s & RBITS) < RFULL &&
U.compareAndSwapLong(this, STATE, s, next = s + RUNIT)) ?
next : acquireRead(false, 0L);
}

3.6 乐观读:tryOptimisticRead() —— 最关键的创新

// StampedLock.java
public long tryOptimisticRead() {
long s;
// 如果写锁未被持有,返回版本号;否则返回 0
return (((s = state) & WBIT) == 0L) ? (s & SBITS) : 0L;
}

乐观读的核心:

  • 不加锁:不修改 state,没有 CAS 操作,不阻塞任何线程
  • 获取版本号:返回当前 state 的版本号(清除低 7 位)
  • 后续校验:读取数据后,调用 validate()检查版本号是否变化

// StampedLock.java
public boolean validate(long stamp) {
U.loadFence(); // 内存屏障,保证读取操作完成
return (stamp & SBITS) == (state & SBITS);
// 比较版本号是否一致
// 如果一致 → 读取期间没有写操作,数据有效
// 如果不一致 → 有写操作发生,需要重读
}

3.7 乐观读的标准使用模式

class Point {
private double x, y;
private final StampedLock sl = new StampedLock();

// 写操作(使用写锁)
void move(double deltaX, double deltaY) {
long stamp = sl.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(stamp);
}
}

// 读操作(使用乐观读)
double distanceFromOrigin() {
// ⭐ 第一阶段:乐观读(不加锁)
long stamp = sl.tryOptimisticRead();
double currentX = x;
double currentY = y;

// ⭐ 第二阶段:验证
if (!sl.validate(stamp)) {
// 乐观读失败 → 升级为悲观读锁
stamp = sl.readLock();
try {
currentX = x;
currentY = y;
} finally {
sl.unlockRead(stamp);
}
}

return Math.sqrt(currentX * currentX + currentY * currentY);
}
}

执行流程:

乐观读成功路径(无竞争):
① stamp = tryOptimisticRead() ← 获取版本号,0 开销
② currentX = x ← 读取数据(无锁)
③ currentY = y ← 读取数据(无锁)
④ validate(stamp) == true ← 校验通过!数据有效
⑤ 使用数据 ← 全程无锁,无 CAS

乐观读失败路径(有写操作介入):
① stamp = tryOptimisticRead() ← 获取版本号
② currentX = x ← 读取数据
③ // 写线程在此刻修改了 x 和 y
④ validate(stamp) == false ← 校验失败!
⑤ stamp = readLock() ← 升级为悲观读锁
⑥ currentX = x ← 重读
⑦ currentY = y
⑧ unlockRead(stamp) ← 释放读锁

3.8 乐观读的陷阱:数据不一致

乐观读的最大陷阱:读取的多个字段可能来自不同的写操作版本!

// ❌ 错误使用:多个字段分别校验
double badDistanceFromOrigin() {
long stamp = sl.tryOptimisticRead();
double currentX = x;
if (!sl.validate(stamp)) {
stamp = sl.readLock();
try { currentX = x; } finally { sl.unlockRead(stamp); }
// 只重读了 x,y 还是旧的!
}
double currentY = y; // y 可能来自不同的版本!
// 如果 x 是写线程A更新后的值,y 是写线程B更新后的值
// 则 (x, y) 可能从未同时存在过!
return Math.sqrt(currentX * currentX + currentY * currentY);
}

// ✅ 正确使用:一次乐观读,一次校验
double goodDistanceFromOrigin() {
long stamp = sl.tryOptimisticRead();
double currentX = x;
double currentY = y;
if (!sl.validate(stamp)) { // 一次校验两个字段
stamp = sl.readLock();
try {
currentX = x;
currentY = y;
} finally {
sl.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}

根本原因:乐观读不阻止写操作,在 tryOptimisticRead()和 validate()之间,可能发生了多次写操作。一次 validate()校验保证了所有读取操作发生在一个一致的时间点内。如果把读取和校验拆开,就会读到来自不同写版本的数据组合。

3.9 乐观读升级为悲观读的优化

JDK 8 提供了 tryConvertToReadLock(),可以在乐观读失败时直接升级,避免二次竞争:

double distanceFromOriginOptimized() {
long stamp = sl.tryOptimisticRead();
double currentX = x;
double currentY = y;

if (!sl.validate(stamp)) {
// ⭐ 尝试将乐观读升级为悲观读锁
stamp = sl.tryConvertToReadLock(stamp);
if (stamp == 0L) { // 升级失败(写锁被其他线程持有)
stamp = sl.readLock(); // 重新获取读锁
}
try {
currentX = x;
currentY = y;
} finally {
sl.unlockRead(stamp);
}
}

return Math.sqrt(currentX * currentX + currentY * currentY);
}

tryConvertToReadLock() 的优势:如果当前写锁没有被其他线程持有,可以直接将乐观读"升级"为读锁,避免一次完整的 readLock()竞争。

3.10 StampedLock 的不可重入性

重要限制:StampedLock 不支持重入!

// ❌ 错误:StampedLock 不可重入
StampedLock lock = new StampedLock();
long stamp1 = lock.writeLock();
long stamp2 = lock.writeLock(); // 死锁!自己阻塞自己
lock.unlockWrite(stamp2);
lock.unlockWrite(stamp1);

// ✅ ReentrantReadWriteLock 支持重入
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
rwl.writeLock().lock();
rwl.writeLock().lock(); // 允许,state = 2
rwl.writeLock().unlock();
rwl.writeLock().unlock();

为什么不可重入? 因为 StampedLock的设计目标是极致性能,重入检查会带来额外的开销。如果需要重入,应使用 ReentrantReadWriteLock。

3.11 StampedLock 不支持条件变量

// ❌ StampedLock 没有 Condition
StampedLock lock = new StampedLock();
Condition cond = lock.newCondition(); // 编译错误!没有这个方法

// ✅ ReentrantLock / ReentrantReadWriteLock 支持
ReentrantLock lock = new ReentrantLock();
Condition cond = lock.newCondition();


四、三种锁的性能对比

4.1 测试场景

测试环境:JDK 17, 8 核 CPU
场景:读多写少,90% 读操作,10% 写操作
临界区:读取/写入两个 double 字段

相对吞吐量(越高越好):

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

4.2 性能分析

场景最佳选择原因
纯读 / 几乎不写 StampedLock 乐观读 零 CAS,零阻塞,极致性能
读多写少 StampedLock 乐观读 + 悲观读降级 绝大多数读取走乐观读路径
读写均衡 ReentrantReadWriteLock 写锁获取开销稳定
写多读少 ReentrantLock 读写锁的维护开销超过收益
需要可重入 ReentrantReadWriteLock StampedLock 不支持重入
需要条件变量 ReentrantLock / ReentrantReadWriteLock StampedLock 不支持

五、实际应用场景与代码示例

5.1 缓存系统

class Cache<K, V> {
private final StampedLock lock = new StampedLock();
private final HashMap<K, V> map = new HashMap<>();

// 读操作(乐观读)
public V get(K key) {
long stamp = lock.tryOptimisticRead();
V value = map.get(key);
if (!lock.validate(stamp)) {
stamp = lock.readLock();
try {
value = map.get(key);
} finally {
lock.unlockRead(stamp);
}
}
return value;
}

// 写操作
public void put(K key, V value) {
long stamp = lock.writeLock();
try {
map.put(key, value);
} finally {
lock.unlockWrite(stamp);
}
}

// 批量更新(写锁)
public void putAll(Map<K, V> entries) {
long stamp = lock.writeLock();
try {
map.putAll(entries);
} finally {
lock.unlockWrite(stamp);
}
}
}

5.2 配置中心

class ConfigurationManager {
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private Properties config = new Properties();

// 读配置(多个线程可以同时读)
public String getConfig(String key) {
rwl.readLock().lock();
try {
return config.getProperty(key);
} finally {
rwl.readLock().unlock();
}
}

// 更新配置(需要写锁,且更新后可选择锁降级)
public void updateConfig(String key, String value) {
rwl.writeLock().lock();
try {
config.setProperty(key, value);
// 锁降级:如果需要后续读取验证
rwl.readLock().lock();
} finally {
rwl.writeLock().unlock(); // 写锁释放
}

try {
// 验证更新结果(此时持有读锁)
String updated = config.getProperty(key);
notifyListeners(key, updated);
} finally {
rwl.readLock().unlock();
}
}

// 批量重载配置
public void reloadConfig(InputStream in) {
rwl.writeLock().lock();
try {
Properties newConfig = new Properties();
newConfig.load(in);
config = newConfig; // 原子替换引用
} catch (IOException e) {
// 处理异常
} finally {
rwl.writeLock().unlock();
}
}
}

5.3 统计计数器(读写锁的读锁重入)

class StatsCounter {
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private final Lock readLock = rwl.readLock();
private final Lock writeLock = rwl.writeLock();

private long successCount;
private long failureCount;
private long totalLatency;

// 记录成功
public void recordSuccess(long latency) {
writeLock.lock();
try {
successCount++;
totalLatency += latency;
} finally {
writeLock.unlock();
}
}

// 记录失败
public void recordFailure() {
writeLock.lock();
try {
failureCount++;
} finally {
writeLock.unlock();
}
}

// 获取快照(读锁保护,多个读线程可并发)
public StatsSnapshot getSnapshot() {
readLock.lock();
try {
return new StatsSnapshot(
successCount,
failureCount,
totalLatency
);
} finally {
readLock.unlock();
}
}

// 获取快照(使用乐观读,更高效)
public StatsSnapshot getSnapshotOptimistic() {
StampedLock sl = new StampedLock(); // 假设改用 StampedLock
// … 乐观读实现
return null;
}

record StatsSnapshot(long success, long failure, long latency) {}
}


六、常见陷阱与注意事项

6.1 StampedLock 的 stamp 不可复用

StampedLock lock = new StampedLock();

// ❌ 错误:stamp 过期后不可再次使用
long stamp = lock.writeLock();
lock.unlockWrite(stamp);
lock.unlockWrite(stamp); // 异常!stamp 已过期

// ✅ 正确:每次获取锁都返回新的 stamp
long stamp1 = lock.writeLock();
// … 操作
lock.unlockWrite(stamp1);
long stamp2 = lock.writeLock(); // 重新获取
// …
lock.unlockWrite(stamp2);

6.2 StampedLock 的不可重入导致死锁

StampedLock lock = new StampedLock();

// ❌ 错误:不可重入导致死锁
void methodA() {
long stamp = lock.writeLock();
try {
methodB(); // methodB 也需要写锁 → 死锁!
} finally {
lock.unlockWrite(stamp);
}
}

void methodB() {
long stamp = lock.writeLock(); // 自己阻塞自己
try { /* … */ }
finally { lock.unlockWrite(stamp); }
}

// ✅ 解决方案:使用可重入的 ReentrantReadWriteLock
// 或者避免在持有写锁时调用需要写锁的方法

6.3 锁降级中的 ABA 问题

// 虽然读写锁本身不会出现 ABA 问题
// 但使用乐观读时需要注意读取的数据一致性
// 见 3.8 节的详细说明

6.4 写锁饥饿

// 非公平模式下的 ReentrantReadWriteLock
// 如果读锁持续被获取,写锁可能永远获取不到

// 解决方案:
// 1. 使用公平锁 new ReentrantReadWriteLock(true)
// 2. StampedLock 通过悲观读时检查等待队列来避免写锁饥饿


七、三把锁的选择决策树

在这里插入图片描述


八、总结

核心要点回顾

知识点一句话总结
读写状态分离 ReentrantReadWriteLock 用 state的高 16 位存读锁数,低 16 位存写锁重入数
读读不互斥 多个读锁可以同时持有,提高读并发
读写互斥 读锁和写锁互斥,保证写操作时数据一致
锁降级 持有写锁时可获取读锁,释放写锁后降级为读锁
写锁饥饿 非公平模式下,持续读锁可能导致写锁无法获取
StampedLock 乐观读 不加锁、不 CAS,通过版本号校验数据一致性
乐观读的校验 一次 validate()保护一次完整读取,不可拆分
StampedLock 限制 不可重入、不支持条件变量

性能选择原则

读:写比例 推荐方案
──────────────────────────────────
100:0 StampedLock.tryOptimisticRead()
90:10 StampedLock(乐观读为主,悲观读降级)
70:30 ReentrantReadWriteLock
50:50 ReentrantLock
30:70 ReentrantLock 或 synchronized

赞(0)
未经允许不得转载:171主机测评 » Java 锁机制深度解析(系列四):读写锁与 Stamped...
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址