概述
StampedLock 是基于能力的锁,用三种模式来控制读/写访问。StampedLock 的状态包含了 版本和模式。锁获取方法根据锁的状态返回一个表示和控制访问的标志(stamp),“try”版本的这些方法可能返回一个特殊的值 0 来表示获取失败。锁释放和它的变换方法要求一个标志作为参数,如果它们不符合锁的状态就失败。这三种模式是:
-
写:方法
writeLock可能阻塞等待独占访问,返回一个标志,可用在方法unlockWrite以释放锁。也提供了无时间和带时间版本的tryWriteLock方法。当锁以写模式持有时,没有读锁可以获取,所有乐观性读确认将失败。 -
读:方法
readLock可能为非独占访问而阻塞等待,返回一个标志用于方法unlockRead以释放锁。也提供了无时间和带时间版本的tryWriteLock方法。 -
乐观读:只有在锁当前没有以写模式持有时,方法
tryOptimisticRead返回一个非0标志。如果锁自给定标志以来没有以写模式持有,方法validate返回true。这种模式可以认为是一种极弱版本的读锁,可以在任意时间被写者打破。在短的只读代码段使用乐观模式常常可以减少竞争和提升吞吐量。然而,它的使用天生是脆弱的。乐观读片段section应该只读字段并持有到本地变量,用于以后使用,在确认以后。乐观读模式里的字段读取可能很不一致,所以惯例只用于当你对数据表示足够熟悉,可以检查一致性和/或重复调用validate()方法。例如,这些步骤典型地在第一次读取对象或数组引用,然后访问其中字段、元素或方法时要求。
这个类也支持方法来条件地提供三种模式之间的转换:例如,方法 tryConvertToWriteLock 尝试 “升级” 模式,返回一个有效的写标志,如果 1)已经是写模式,2)在读模式且没有其他读者,或者 3)在乐观模式且锁可得。这些方法的形式是设计用于帮助减少一些代码膨胀,否则将出现在基于重试的设计。
StampedLock 设计用作开发线程安全组件的内部工具。它们的使用依赖于对数据、对象和它们所保护方法的内在属性的知识。它们不是可重入的,所以锁保护块不应该调用其他可能尝试再次获取锁(虽然你可以传递标志给其他方法,然后使用或转换它)的未知方法。使用读锁模式依赖于关联的代码片段是无边际效应的(也就是无副作用)。无效的乐观读片段不能调用那些不知道忍受潜在不一致的方法。标志使用有限的表示,且不是密码加密安全的(例如,一个有效的标志是可猜测的)。标志的值可能会循环,在(不早于)一年的持续操作后。持有标志而不使用或确认,在大于这个周期可能将不能正确确认。StampedLock 是可序列化的,但总是反序列化为初始未锁定状态,所以对于远程锁定没有帮助。
StampedLock 的调度策略不是一惯地倾向于选择读者而不是写者,或相反。所有“try”方法都是尽最大努力的,不必向任何调度或公平策略确认。任何用于获取的“try”方法或不带任何关于锁状态的信息的转换锁模式的方法返回 0 ;后续的调用可能成功。
因为它支持协调跨多种锁模式使用,这个类不直接使用 Lock 或 ReadWriteLock 接口。然而,StampedLock 可以看作 asReadLock(), asWriteLock(), 或 asReadWriteLock(),在要求这样一组关联功能的应用里。
使用示例
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;
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) { // upgrade
// Could instead start with optimistic, not read mode
long stamp = sl.readLock();
try {
while (x == 0.0 && y == 0.0) {
long ws = sl.tryConvertToWriteLock(stamp);
if (ws != 0L) {
stamp = ws;
x = newX;
y = newY;
break;
} else {
sl.unlockRead(stamp);
stamp = sl.writeLock();
}
}
} finally {
sl.unlock(stamp);
}
}
}
小结
在大多数都是读取、很少写入的情况下,乐观读锁模式可以极大提供吞吐量(不需要加读锁),也可以减少这种情况下写者饥饿的现象(由于读者一般不加读锁,写者可以马上获取到锁)。但它的使用也是比较复杂的,因为使用乐观锁读取数据之后是要确认一致性,最要命的是它还不是可重入的。
欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。
