您当前的位置: 首页 > 

顧棟

暂无认证

  • 1浏览

    0关注

    227博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

【JUC系列】LOCK框架系列之八 核心锁类之StampedLock

顧棟 发布时间:2022-05-01 06:00:00 ,浏览量:1

StampedLock

文章目录
  • StampedLock
    • 简介
    • 实现分析
      • 核心思想
      • 组成
        • 构造函数
        • 主要成员
          • state
          • WNode
          • whead和wtail
        • 主要方法
      • 数据结构
      • 写锁的获取与释放
        • 写锁的释放
      • 乐观读锁的获取与释放
        • 读锁释放
      • 转换成写锁
      • 转换成读锁
      • 转换成乐观锁锁
    • 遗留问题

简介

ReentrantReadWriteLock 在沒有任何读写锁时,才可以取得写入锁,这可用于实现了悲观读(Pessimistic Reading),即如果执行中进行读取时,经常可能有另一执行要写入的需求,为了保持同步,ReentrantReadWriteLock 的读取锁定就可派上用场。然而,如果读取执行情况很多,写入很少的情况下,使用 ReentrantReadWriteLock 可能会使写入线程遭遇饥饿(Starvation)问题,也就是写线程迟迟无法竞争到锁定而一直处于等待状态。StampedLock可以环境这个问题。

  1. 它是java8在java.util.concurrent.locks新增的一个API。

  2. 它是一种基于性能的锁,具有三种用于控制读/写访问的模式。三种模式是:

    • Writing锁

      writeLock方法可能会阻塞等待独占访问,返回可以在unlockWrite方法中用于释放锁的标记。还提供了不定时和定时版本的 tryWriteLock。当锁保持在写模式时,不可能获得读锁,并且所有乐观读验证将失败。

    • Reading锁

      readLock 方法可能会阻塞等待非独占访问,返回可用于方法 unlockRead 以释放锁的标记。还提供了不定时和定时版本的 tryReadLock。

    • Optimistic Reading

      仅当锁当前未处于写入模式时,方法 tryOptimisticRead 才会返回非零标记。可以通过validate(long stamp)判断这个标记时候是否有写锁。

      这种模式可以被认为是一个非常弱的读锁版本,可以随时被写入者打破。 对简短且只读代码段使用乐观模式通常会减少争用并提高吞吐量。 然而,它的使用本质上是脆弱的。 乐观读取部分应该只读取字段并将它们保存在局部变量中以供验证后使用。 在乐观模式下读取的字段可能非常不一致,因此仅当您对数据表示足够熟悉以检查一致性和/或重复调用方法 validate() 时才适用。 例如,当首先读取对象或数组引用,然后访问其字段、元素或方法之一时,通常需要执行此类步骤。

  3. StampedLock 的状态state由版本和模式组成。

  4. 锁的获取方法返回一个标记,它代表和控制与锁状态相关的访问; 这些方法的“try”版本可能会返回特殊值零来表示无法获取访问权限。锁释放和转换方法需要标记作为参数,如果它们与锁的状态不匹配,则会失败。

  5. 此类还支持有条件地提供跨三种模式的转换的方法。 例如,方法 tryConvertToWriteLock 尝试“升级”模式,如果 (1) 已经处于写入模式 (2) 处于读取模式并且没有其他读取器或 (3) 处于乐观模式并且锁可用,则返回有效的写入标记 . 这些方法的形式旨在帮助减少在基于重试的设计中出现的一些代码膨胀。

  6. StampedLocks 旨在用作开发线程安全组件的内部实用程序。它们的使用依赖于对它们所保护的数据、对象和方法的内部属性的了解。它们不是可重入的,因此锁定的主体不应调用其他可能尝试重新获取锁的未知方法(尽管您可以将标记传递给可以使用或转换它的其他方法)。读锁模式的使用依赖于相关的代码段是无副作用的。未经验证的乐观读取部分不能调用未知的方法来容忍潜在的不一致。Stamps使用有限的表示,并且在密码学上不安全(即,有效的Stamps 可能是可猜测的)。Stamps 值可以在(不早于)连续运行一年后回收。超过此期限而未使用或验证的Stamps 可能无法正确验证。 StampedLocks 是可序列化的,但总是反序列化为初始解锁状态,因此它们对于远程锁定没有用处。

下面是java doc提供的StampedLock一个例子

import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.StampedLock;

public class Point {
        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);
            }
        }

        double distanceFromOrigin() { // A read-only method 读锁
            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);
            }
        }
}
实现分析

| 或运算 逢1得1,其他得0。 & 与运算 逢0得0,其他得1。 ~ 非运算 0变1,1变0 。

核心思想

如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。同样使用CLH列队进行锁实现。不同与AQS的是,如果是写请求或者悲观读请求,则在获取不到的情况下进行队列阻塞。每个写请求都是一个结点与读结点形成一个"读写相间链表“,而读与读之间是在"读写相间链表“上的读结点上新增一个读链表。具体可见数据结构上的图。

组成 构造函数
    public StampedLock() {
        // 初始化同步状态值
        state = ORIGIN;
    }
主要成员 state

锁的序列亦或状态

/** Lock sequence/state */
private transient volatile long state;

在这个类中,同步状态为采用了long型state,它拥有读锁,写锁,数据版本三个含义。

    /** The number of bits to use for reader count before overflowing */
    // 读锁占用的位数
    private static final int LG_READERS = 7;

    // Values for lock state and stamp operations
    // 一个读状态单位
    private static final long RUNIT = 1L;
    // 写状态标识位
    private static final long WBIT  = 1L             
关注
打赏
1663402667
查看更多评论
0.0446s