StampedLock 是 Java 8 引入的一种改进的读写锁,它提供了三种模式(写锁、悲观读锁、乐观读),并且性能比 ReentrantReadWriteLock 更好。下面是详细的使用指南:
1. 三种锁模式
a) 写锁 (Write Lock)
- 独占锁,类似
ReentrantReadWriteLock的写锁 - 使用
writeLock()获取,返回一个 stamp - 必须使用对应的 stamp 解锁
b) 悲观读锁 (Pessimistic Read Lock)
- 共享锁,类似
ReentrantReadWriteLock的读锁 - 使用
readLock()获取 - 阻塞写锁,不阻塞其他读锁
c) 乐观读 (Optimistic Read)
- 无锁操作,不阻塞任何锁
- 使用
tryOptimisticRead()获取 stamp - 需要验证数据是否被修改
2. 基本使用示例
java
import java.util.concurrent.locks.StampedLock;
public class StampedLockExample {
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.readLock(); // 获取悲观读锁
try {
return Math.sqrt(x * x + y * y);
} finally {
sl.unlockRead(stamp); // 释放读锁
}
}
// 乐观读操作 - 尝试读取
void moveIfAtOrigin(double newX, double newY) {
// 乐观读(不获取锁)
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);
}
}
// 业务逻辑:如果位于原点则移动
if (currentX == 0.0 && currentY == 0.0) {
long writeStamp = sl.writeLock();
try {
if (x == 0.0 && y == 0.0) {
x = newX;
y = newY;
}
} finally {
sl.unlockWrite(writeStamp);
}
}
}
}
3. 高级特性示例
a) 尝试获取锁
java
public class StampedLockAdvanced {
private final StampedLock lock = new StampedLock();
private String data = "initial";
// 尝试获取写锁(非阻塞)
public boolean tryWrite(String newData) {
long stamp = lock.tryWriteLock();
if (stamp != 0L) {
try {
data = newData;
return true;
} finally {
lock.unlockWrite(stamp);
}
}
return false;
}
// 尝试获取读锁(带超时)
public String tryReadWithTimeout() throws InterruptedException {
long stamp = lock.tryReadLock(1, java.util.concurrent.TimeUnit.SECONDS);
if (stamp != 0L) {
try {
return data;
} finally {
lock.unlockRead(stamp);
}
}
return "timeout";
}
// 锁升级(读锁 -> 写锁)
public boolean upgradeReadToWrite(String newData) {
long stamp = lock.readLock();
try {
// 检查条件
if (data.equals("upgrade")) {
// 尝试升级为写锁
long writeStamp = lock.tryConvertToWriteLock(stamp);
if (writeStamp != 0L) {
stamp = writeStamp; // 升级成功
data = newData;
return true;
} else {
// 升级失败,释放读锁,重新获取写锁
lock.unlockRead(stamp);
stamp = lock.writeLock();
data = newData;
return true;
}
}
return false;
} finally {
lock.unlock(stamp); // 统一解锁
}
}
}
b) 乐观读模式的最佳实践
java
public class Point {
private double x, y;
private final StampedLock sl = new StampedLock();
// 使用乐观读的最佳实践
double distanceFromOriginOptimistic() {
// 第一次尝试:乐观读
long stamp = sl.tryOptimisticRead();
double currentX = x, 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);
}
// 复合操作示例
void moveIfAt(double oldX, double oldY, double newX, double newY) {
// 重试机制
for (int attempt = 0; attempt < 3; attempt++) {
long stamp = sl.readLock();
try {
// 检查当前值
if (x == oldX && y == oldY) {
long writeStamp = sl.tryConvertToWriteLock(stamp);
if (writeStamp != 0L) {
// 转换成功
stamp = writeStamp;
x = newX;
y = newY;
return;
}
}
} finally {
sl.unlock(stamp);
}
// 短暂休眠后重试
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
}
}
4. 缓存实现示例
java
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.StampedLock;
public class StampedLockCache<K, V> {
private final Map<K, V> map = new HashMap<>();
private final StampedLock lock = new StampedLock();
// 写操作
public void put(K key, V value) {
long stamp = lock.writeLock();
try {
map.put(key, value);
} finally {
lock.unlockWrite(stamp);
}
}
// 读操作(悲观读)
public V get(K key) {
long stamp = lock.readLock();
try {
return map.get(key);
} finally {
lock.unlockRead(stamp);
}
}
// 读操作(乐观读) - 适合读多写少的场景
public V getOptimistic(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 boolean updateIfPresent(K key, V newValue) {
long stamp = lock.readLock();
try {
if (map.containsKey(key)) {
long writeStamp = lock.tryConvertToWriteLock(stamp);
if (writeStamp != 0L) {
stamp = writeStamp;
map.put(key, newValue);
return true;
} else {
// 转换失败,释放读锁,获取写锁
lock.unlockRead(stamp);
stamp = lock.writeLock();
if (map.containsKey(key)) {
map.put(key, newValue);
return true;
}
}
}
} finally {
lock.unlock(stamp);
}
return false;
}
}
5. 使用注意事项
优点:
- 性能更好:乐观读模式在读多写少的场景下性能优异
- 避免死锁:锁不可重入(但可以通过记录 stamp 实现类似效果)
- 灵活的锁转换:支持读锁到写锁的转换
注意事项:
- 不可重入:同一个线程不能重复获取锁(会死锁)
- Stamp 管理:必须正确保存和验证 stamp
- 中断处理:不支持锁获取时的中断
- 条件变量:没有内置的条件变量支持
- 避免长时间持有锁:写锁会阻塞所有读操作
最佳实践:
java
// 模板模式:确保锁的正确释放
public class LockTemplate {
public static void withWriteLock(StampedLock lock, Runnable task) {
long stamp = lock.writeLock();
try {
task.run();
} finally {
lock.unlockWrite(stamp);
}
}
public static <T> T withReadLock(StampedLock lock, Supplier<T> supplier) {
long stamp = lock.readLock();
try {
return supplier.get();
} finally {
lock.unlockRead(stamp);
}
}
}
StampedLock 在读多写少的高并发场景下性能优势明显,但需要注意它的不可重入性和 stamp 的正确管理。