StampedLock
是 Java 8 引入的一种新的锁机制,位于 java.util.concurrent.locks
包下。它可以被认为是 ReadWriteLock
的一个改进版,提供了一种乐观的读锁策略,这种策略可以在某些场景下减少锁的竞争,从而提高性能。与 ReadWriteLock
相比,StampedLock
在使用时需要更加注意,因为它的锁方法不支持重入,并且不支持条件变量。
工作原理
StampedLock
提供了三种主要的访问模式:
-
写锁(Write Lock):
- 类似于
ReadWriteLock
的写锁,提供了排他性的访问。 - 获取写锁时,
StampedLock
会阻塞读锁和写锁的获取。
- 类似于
-
悲观读锁(Pessimistic Read Lock):
- 类似于
ReadWriteLock
的读锁,但在StampedLock
中被称为悲观读锁。 - 当获取到悲观读锁时,写锁的请求会被阻塞,直到所有悲观读锁都释放。
- 类似于
-
乐观读(Optimistic Read):
- 这是
StampedLock
独有的一种读取模式。 - 乐观读不会阻塞写锁的获取,也不会获取实际的锁;它会返回一个邮戳(stamp),代表这次读的版本。
- 在数据实际读取之前,需要检查邮戳,看看在读取过程中有没有写操作进行,如果有,乐观读需要退化为悲观读锁或者重新读取数据。
- 这是
锁的获取和释放
每次锁的获取操作,无论是读锁还是写锁,StampedLock
都会返回一个表示锁状态的邮戳(stamp)。这个邮戳在锁的释放时必须要提供,作为释放锁的凭证。这是一个与 ReadWriteLock
显著不同的特点,因为 ReadWriteLock
并不需要这样的邮戳。
重要特点
-
非阻塞的乐观读 :
StampedLock
支持一种非阻塞的读取锁,这可以减少线程阻塞,提高系统吞吐量。 -
不支持条件变量 :
StampedLock
不支持条件变量。如果需要类似条件变量的特性,需要使用ReentrantLock
或者ReadWriteLock
。 -
不支持重入 :
StampedLock
的锁不支持重入。如果一个线程已经持有锁,它不能再次获取锁,否则可能会导致死锁。 -
中断敏感性 :
StampedLock
的锁获取方法提供中断敏感和非中断敏感的版本,这意味着获取锁的操作可以响应中断。 -
锁的转换 :
StampedLock
允许从读锁转换为写锁,反之亦然,通过使用特定的方法来尝试转换锁,并检查返回的邮戳是否有效。
示例代码
下面展示了一个使用 StampedLock
的简单示例,包括悲观读锁和写锁的使用:
java
import java.util.concurrent.locks.StampedLock;
public class StampedLockExample {
private final StampedLock sl = new StampedLock();
// 模拟共享资源
private int sharedState;
public void write(int newValue) {
long stamp = sl.writeLock(); // 获取写锁,并返回邮戳
try {
sharedState = newValue;
} finally {
sl.unlockWrite(stamp); // 释放写锁,并提供邮戳
}
}
public int read() {
long stamp = sl.readLock(); // 获取悲观读锁,并返回邮戳
try {
return sharedState;
} finally {
sl.unlockRead(stamp); // 释放读锁,并提供邮戳
}
}
public int optimisticRead() {
long stamp = sl.tryOptimisticRead(); // 尝试获取乐观读,并返回邮戳
int currentState = sharedState; // 读取共享资源
if (!sl.validate(stamp)) { // 检查在读取过程中是否有写操作
stamp = sl.readLock(); // 退化为悲观读锁
try {
currentState = sharedState;
} finally {
sl.unlockRead(stamp);
}
}
return currentState;
}
}
在这个示例中,我们展示了如何在 StampedLock
中获取和释放写锁和悲观读锁,以及如何执行乐观读取并验证它是否仍然有效。注意:在实际使用中,你需要小心地管理锁的释放,以避免死锁和资源泄露。