StampedLock 类是 Java8 中引入的一种新的同步原语,旨在提供比传统读写锁更高的性能,特别是在读操作远多于写操作的场景中。
StampedLock 类是一种基于能力的锁,有三种控制读/写访问的模式。StampedLock 的状态由版本和模式组成。获取锁的方法会返回一个“印戳”,代表并控制与锁状态相关的访问;这些方法的 “try” 版本可能会返回特殊值 0,代表获取访问失败。锁的释放和转换方法需要将“印戳”作为参数,如果“印戳”与锁的状态不匹配则会失败。
StampedLock 类的三种模式:
写模式(Write Mode):方法 writeLock() 可能会阻塞等待独占访问,并返回一个“印戳”,该“印戳”可用于方法 unlockWrite(long) 来释放锁。tryWriteLock() 方法提供了限时和非限时阻塞版本。当锁处于写入模式时,无法获得读取锁,所有乐观的读取验证都将失败。也就是说,在这种模式下,锁被单个线程独占,用于修改数据。其他线程无法获取写锁或读锁,直到该线程释放写锁。
悲观读模式(Pessimistic Read Mode):方法 readLock() 可能会阻塞等待非独占访问,返回一个“印戳”,“印戳” 可用于方法 unlockRead(long) 释放锁。tryReadLock 也提供了限时和非限时阻塞版本。注意:在这种模式下,线程会阻塞其他尝试获取写锁的线程,但不会阻塞其他尝试获取读锁的线程。它允许线程在读取数据时不被打断,但可能会降低写操作的并发性。
乐观读模式(Optimistic Read Mode):方法 tryOptimisticRead() 只有在锁当前未处于写模式时才会返回非零“印戳”。方法 validate(long) 返回 true,前提是在获得给定“印戳”后,锁尚未在写模式下被获取。这种模式可视为读锁的极弱版本,随时可能被写入者破坏。在较短的只读代码段中使用乐观模式通常能减少争用,提高吞吐量。然而,使用这种模式本身就很脆弱。乐观模式读取的代码段只能读取字段,并将其保存在局部变量中,以供验证后使用。在乐观模式下读取的字段可能会严重不一致,因此只有在对数据表示足够熟悉,能够检查一致性和/或反复调用方法 validate() 时才可使用。例如,在首先读取对象或数组引用,然后访问其中一个字段、元素或方法时,通常需要采取这些步骤。
StampedLock 类还支持有条件地提供三种模式转换的方法。例如,方法 tryConvertToWriteLock(long) 尝试“升级”一种模式,在以下情况下返回有效的写“印戳”:
(1)自身已处于写模式;
(2)自身处于读模式且没有其他线程在写模式行下获取锁;
(3)自身处于乐观模式且锁可用。
这些方法的设计形式有助于减少基于重试的设计中出现的代码臃肿现象。
StampedLock 类是用于开发线程安全组件的内部工具。它们的使用依赖于对所保护的数据、对象和方法的内部属性的了解。它不是可重入的,因此被锁定的主体不应调用其他可能试图重新获取锁的未知方法(尽管你可以将“印戳”传递给其他可以使用或转换“印戳”的方法)。读锁模式的使用依赖于相关代码段不产生副作用。未经验证的乐观锁读取部分不能调用不知道是否能容忍潜在不一致的方法。“印戳”使用有限表示法,在密码学上并不安全(即一个有效的“印戳”可能是可猜测的)。“印戳”值可在连续运行一年后回收,超过此期限而未使用或验证的“印戳”可能无法正确验证。
StampedLock 可序列化,但始终会反序列化为初始解锁状态,因此不能用于远程锁定。
StampedLock 的调度策略并不总是优先于写入者,反之亦然。所有“try”方法都是尽力而为的,并不一定符合任何调度或公平策略。从任何获取或转换锁的“try”方法中返回的零并不携带任何有关锁状态的信息,后续调用可能会成功。
由于该类支持多种锁模式的协调使用,因此没有直接实现 Lock 或 ReadWriteLock 接口。不过,在只需要相关功能的应用程序中,StampedLock 可被视为读锁(ReadLock())、写锁(WriteLock())或读写锁(ReadWriteLock())。
下面是官方文档提供的一个示例:
// 表示一个点 class Point { // 点的坐标(x,y) private double x, y; // 锁对象 private final StampedLock sl = new StampedLock(); // 移动点的位置 void move(double deltaX, double deltaY) { // an exclusively locked method // 获取写入锁 long stamp = sl.writeLock(); try { x += deltaX; y += deltaY; } finally { // 释放写入锁 sl.unlockWrite(stamp); } } // 计算与原点(0,0)的距离 double distanceFromOrigin() { // A read-only method // 获取乐观读锁 long stamp = sl.tryOptimisticRead(); // 获取当前的坐标值x,y 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); } // 如果在原点(0,0),则移动到指定位置(newX, newY) 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); } } }