概述
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笔记,可以更及时回复你的讨论。