Java8 StampedLock

概述

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 ;后续的调用可能成功。

因为它支持协调跨多种锁模式使用,这个类不直接使用 LockReadWriteLock 接口。然而,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笔记,可以更及时回复你的讨论。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据