StampedLock与读写锁优化

StampedLock与读写锁优化

StampedLock 是 Java 8 引入的新型锁,相比 ReentrantReadWriteLock 提供了乐观读能力,在读多写少的场景下性能更优。

三种锁模式

public class StampedLock implements java.io.Serializable {
// 写锁
public long writeLock();
public void unlockWrite(long stamp);

// 悲观读锁
public long readLock();
public void unlockRead(long stamp);

// 乐观读
public long tryOptimisticRead();
public boolean validate(long stamp);
}

ReentrantReadWriteLock 回顾

public class Point {
private double x, y;
private final ReadWriteLock rwl = new ReentrantReadWriteLock();
private final Lock r = rwl.readLock();
private final Lock w = rwl.writeLock();

public double distance() {
r.lock();
try {
return Math.sqrt(x * x + y * y);
} finally {
r.unlock();
}
}

public void move(double dx, double dy) {
w.lock();
try {
x += dx;
y += dy;
} finally {
w.unlock();
}
}
}

缺点:读锁会阻塞写锁,读操作频繁时写操作可能饥饿。

StampedLock 乐观读

基本用法

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

// 乐观读
public 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);
}

// 写操作
public void move(double dx, double dy) {
long stamp = sl.writeLock();
try {
x += dx;
y += dy;
} finally {
sl.unlockWrite(stamp);
}
}
}

锁升级

public void transform(double dx, double dy) {
long stamp = sl.readLock(); // 先获取读锁
try {
while (x == 0.0 && y == 0.0) {
long ws = sl.tryConvertToWriteLock(stamp); // 尝试升级为写锁
if (ws != 0L) {
stamp = ws;
x = dx;
y = dy;
break;
} else {
sl.unlockRead(stamp);
stamp = sl.writeLock(); // 直接获取写锁
}
}
} finally {
sl.unlock(stamp); // 统一释放
}
}

三种模式对比

特性 ReentrantReadWriteLock StampedLock
读锁 阻塞写 阻塞写(悲观读)
乐观读 不支持 支持(不阻塞写)
重入 支持 不支持
条件变量 支持 不支持
性能 一般 读多写少时更好
中断 支持 乐观读不支持

性能测试

public class LockPerformance {
public static void main(String[] args) {
// 读:写 = 100:1 场景
testReadWriteLock();
testStampedLock();
}
}

结果

  • 读多写少(100:1):StampedLock 比 ReadWriteLock 快 5~10 倍
  • 读写均衡:性能相近
  • 写多读少:ReadWriteLock 略优

使用陷阱

1. 不可重入

public void methodA() {
long stamp = lock.readLock();
try {
methodB(); // 同一线程再次获取锁会死锁!
} finally {
lock.unlockRead(stamp);
}
}

public void methodB() {
long stamp = lock.readLock(); // 死锁!
// ...
}

2. 乐观读validate后不能再用旧数据

long stamp = lock.tryOptimisticRead();
int value = data.get();

// ... 其他操作 ...

if (lock.validate(stamp)) {
// 错误!validate后value可能已被修改
return value * 2;
}

3. 忘记验证

long stamp = lock.tryOptimisticRead();
// 直接读取,没有validate!
return x + y;

4. 锁升级问题

// 错误:先获取读锁,另一个线程获取写锁,尝试升级会死锁
long stamp = lock.readLock();
try {
if (needWrite) {
// 如果有其他线程持有写锁,这里会死锁
long ws = lock.tryConvertToWriteLock(stamp);
}
} finally {
lock.unlockRead(stamp);
}

最佳实践

1. 读多写少才用乐观读

public class OptimizedCache {
private final StampedLock lock = new StampedLock();
private Map<String, Object> cache = new HashMap<>();

public Object get(String key) {
long stamp = lock.tryOptimisticRead();
Object value = cache.get(key);

if (!lock.validate(stamp)) {
stamp = lock.readLock();
try {
value = cache.get(key);
} finally {
lock.unlockRead(stamp);
}
}

return value;
}

public void put(String key, Object value) {
long stamp = lock.writeLock();
try {
cache.put(key, value);
} finally {
lock.unlockWrite(stamp);
}
}
}

2. 快速路径 + 慢速路径

public double read() {
// 快速路径:乐观读
long stamp = sl.tryOptimisticRead();
double result = doRead();
if (sl.validate(stamp)) {
return result; // 成功,直接返回
}

// 慢速路径:悲观读
stamp = sl.readLock();
try {
return doRead();
} finally {
sl.unlockRead(stamp);
}
}

3. 不要混合使用不同锁

// 错误:混用StampedLock和内置锁
private final StampedLock sl = new StampedLock();
private final Object lock = new Object();

总结

场景 推荐锁
读多写少 + 无需重入 StampedLock(乐观读)
需要重入 ReentrantReadWriteLock
需要条件变量 ReentrantReadWriteLock
写多读少 ReentrantReadWriteLock

StampedLock 是读多写少场景的利器,但使用复杂度较高。只有在确实需要极致读性能时,才值得使用它。


   转载规则


《StampedLock与读写锁优化》 小乐 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录