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) { 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)) { return value * 2; }
|
3. 忘记验证
long stamp = lock.tryOptimisticRead();
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. 不要混合使用不同锁
private final StampedLock sl = new StampedLock(); private final Object lock = new Object();
|
总结
| 场景 |
推荐锁 |
| 读多写少 + 无需重入 |
StampedLock(乐观读) |
| 需要重入 |
ReentrantReadWriteLock |
| 需要条件变量 |
ReentrantReadWriteLock |
| 写多读少 |
ReentrantReadWriteLock |
StampedLock 是读多写少场景的利器,但使用复杂度较高。只有在确实需要极致读性能时,才值得使用它。