StampedLock 是 Java 8 引入的高性能读写锁,通过乐观读机制优化读多写少场景的并发性能。以下是全面解析:
🔧 一、基本原理
-
核心设计
- 三种锁模式:
- 写锁(Write Lock):独占锁,阻塞所有读写操作(类似
ReentrantLock)。 - 悲观读锁(Pessimistic Read Lock):共享锁,允许多线程并发读,但阻塞写操作(类似
ReentrantReadWriteLock读锁)。 - 乐观读锁(Optimistic Read):无锁读取,仅通过版本戳(Stamp)验证数据一致性,不阻塞写操作。
- 写锁(Write Lock):独占锁,阻塞所有读写操作(类似
- 票据(Stamp)机制:
- 每次获取锁返回一个
long型票据,用于释放锁或验证数据一致性。 - 写操作会更新票据版本,使之前的乐观读票据失效。
- 每次获取锁返回一个
- 底层实现:基于 CLH 队列变种(非 AQS),通过自旋和状态位管理锁竞争。
- 三种锁模式:
-
锁转换
- 支持锁升级(如读锁→写锁)和降级(写锁→读锁),通过
tryConvertToWriteLock()等 API 实现。
- 支持锁升级(如读锁→写锁)和降级(写锁→读锁),通过
🛠️ 二、使用方式与代码示例
1. 基础操作
import java.util.concurrent.locks.StampedLock;
public class Point {
private double x, y;
private final StampedLock lock = new StampedLock();
// 写锁示例
public void move(double deltaX, double deltaY) {
long stamp = lock.writeLock(); // 获取写锁
try {
x += deltaX;
y += deltaY;
} finally {
lock.unlockWrite(stamp); // 释放写锁[3,6](@ref)
}
}
// 乐观读示例
public double distanceFromOrigin() {
long stamp = lock.tryOptimisticRead(); // 获取乐观读票据
double currentX = x, currentY = y;
if (!lock.validate(stamp)) { // 检查期间是否有写操作
stamp = lock.readLock(); // 获取悲观读锁
try {
currentX = x;
currentY = y;
} finally {
lock.unlockRead(stamp); // 释放读锁
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
}
2. 锁升级示例
public void moveIfAtOrigin(double newX, double newY) {
long stamp = lock.readLock(); // 先获取悲观读锁
try {
while (x == 0.0 && y == 0.0) {
long ws = lock.tryConvertToWriteLock(stamp); // 尝试升级为写锁
if (ws != 0L) {
stamp = ws; // 更新票据
x = newX;
y = newY;
break;
} else {
lock.unlockRead(stamp); // 升级失败则显式获取写锁
stamp = lock.writeLock();
}
}
} finally {
lock.unlock(stamp); // 释放锁[4,7](@ref)
}
}
⚖️ 三、优缺点分析
| 维度 | 优点 | 缺点 |
|---|---|---|
| 性能 | 读多写少时吞吐量极高(乐观读无锁)。 | 写频繁时性能差(写锁独占)。 |
| 线程饥饿 | 写锁优先,避免写线程饥饿。 | 乐观读可能需回退到悲观读,增加开销。 |
| 重入性 | - | 不可重入:同一线程重复获取锁会导致死锁。 |
| 功能支持 | 支持锁转换(升级/降级)。 | 不支持条件变量(如 Condition)。 |
| API 复杂度 | - | 需手动管理票据,易出错。 |
🎯 四、适用场景
1. 理想场景
- 读多写少的高并发:如缓存系统(90% 读 + 10% 写)。
- 配置管理:全局配置高频读取,低频更新(如白名单)。
- 数据快照需求:允许短暂数据不一致(如实时计算中间结果)。
2. 不适用场景
- 写操作频繁:写锁竞争导致性能低于
ReentrantLock。 - 需要条件变量:如线程间协调(需改用
ReentrantLock+Condition)。 - 重入需求:如递归调用(需改用
ReentrantReadWriteLock)。
⚠️ 五、注意事项
- 票据管理:
- 释放锁时必须传入正确的票据,否则抛
IllegalMonitorStateException。
- 释放锁时必须传入正确的票据,否则抛
- 乐观读验证:
- 读取数据后必须调用
validate(stamp),失败则回退到悲观读。
- 读取数据后必须调用
- 锁升级风险:
- 锁升级可能失败,需设计重试或备用逻辑。
💎 总结
StampedLock 是 Java 并发编程中的 “锁王”,核心价值在于:
- 乐观读机制:通过无锁读取最大化读并发性能,适合读多写少场景。
- 写优先策略:避免传统读写锁的写饥饿问题。
- 锁转换能力:灵活升级/降级锁模式(需谨慎使用)。
✨ 最佳实践:在缓存、配置管理等读密集型系统中替代
ReentrantReadWriteLock,结合validate()严格验证数据一致性。避免在写操作频繁或需要重入的场景使用。
740

被折叠的 条评论
为什么被折叠?



