StampedLock概述
StampedLock是从JDK1.8开始引入的,它的出现对于ReentrantReadWriteLock在读多写少的情况下的效率问题还有写线程容易产生“饥饿”的问题有了很大的解决。ReentrantReadWriteLock 在沒有任何读写锁时才能获取到写锁,StampedLock认为尝试获取读锁的时候如果有写锁存在不应该是阻塞而是重读,而且在StampedLock的乐观锁的情况下写锁可以直接“闯入”。
StampedLock使用了一个stamp的概念(可以就理解为时间戳),它可以被用作加锁解锁操作的一个票据。
StampedLock的三种状态
- 写入(Writing):writeLock方法获取独占式同步状态而阻塞当前线程,返回一个stamp,这个stamp能够在unlockWrite方法中使用从而释放锁。也提供了tryWriteLock。当锁被写模式所占有,读或者乐观的读操作都不能获取到同步状态。
- 读取(Reading):readLock方法获取共享式同步状态而阻塞随后的写线程,返回一个stamp变量,能够在unlockRead方法中用于释放锁。同时也提供了tryReadLock方法。
- 乐观读取(Optimistic Reading):提供了tryOptimisticRead方法返回一个非0的stamp,只有当前同步状态没有被写模式所占有是才能获取到。如果在获得stamp变量之后没有被写模式持有,方法validate将返回true。这种模式可以被看做一种弱版本的读锁,可以被一个写入者在任何时间打断。乐观读取模式仅用于短时间读取操作时经常能够降低竞争和提高吞吐量。同时使用的时候一般需要读取并存储到另外一个副本,以用做对比使用。
**注意:**StampedLock是不支持重入的。
StampedLock使用
下面是作者给出的使用例子:
class Point {
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.tryOptimisticRead();
//读到内容到副本
double currentX = x, currentY = y;
//调用validate来验证(如果被其他线程修改过了那么stamp就变了将返回false)
if (!sl.validate(stamp)) {
//验证失败了就用读锁(将阻塞写操作)
stamp = sl.readLock();
try {
currentX = x;
currentY = y;
} finally {
//释放读锁
sl.unlockRead(stamp);
}
}
//验证成功直接使用变量值
return Math.sqrt(currentX * currentX + currentY * currentY);
}
void moveIfAtOrigin(double newX, double newY) { // 锁升级
//获取读锁
long stamp = sl.readLock();
try {
//判断某种条件,需要进行写的操作
while (x == 0.0 && y == 0.0) {
//升级为写锁
long ws = sl.tryConvertToWriteLock(stamp);
//如果ws不为零表示升级成功并获取到写锁
if (ws != 0L) {
stamp = ws;
x = newX;
y = newY;
break;
}
//升级失败释放读锁并直接使用写锁
else {
sl.unlockRead(stamp);
stamp = sl.writeLock();
}
}
} finally {
//释放
sl.unlock(stamp);
}
}
}
除了不支持重入之外,网上也有说StampedLock对于带着中断状态的线程将会使得CPU爆满的情况。除此之外StampedLock基本可以代替ReentrantReadWriteLock。