stamplock的相比于readwritelock的乐观锁加强以及它的重入为何不允许

stamplock的相比于readwritelock的乐观锁加强以及它的重入为何不允许,第1张

系列文章目录

提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
stampLock的大致功能预计他的读锁源码解析


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录
  • 系列文章目录
  • 前言
  • 一、stampLock是什么?以及其中的重要常量
  • 二、1.写锁代码以及源码解析
    • 2.读锁代码
    • 读锁源码部分解析
    • 3.读锁升级代码
    • 4.核心方法
  • 总结


前言

提示:这里可以添加本文要记录的大概内容:

前不久和朋友讨论了一波stamplock的功能以及他的锁重入问题;于是乎跟他一起看起了源码;很折磨!!!


提示:以下是本篇文章正文内容,下面案例可供参考

一、stampLock是什么?以及其中的重要常量

stampLock其实可以简单地看成readWriteLock的一种加强
对于读锁的加强,他的读锁从原本的悲观锁变成了读锁。

要读懂源码 首先要把代码中的常用变量给理解清楚 并大概的记下来 才容易理解源码内容
废话不多看源码

引用的关键值

//类序列号
 private static final long serialVersionUID = -6001602636862214147L;

    /** 程序的序列号, 用来cpu线程循环的控制 */
    private static final int NCPU = Runtime.getRuntime().availableProcessors();

    /** 最大的队列询问重试值2^6; ncpu为程序设置的cpu线程数;at least 1 */
    private static final int SPINS = (NCPU > 1) ? 1 << 6 : 1;

    /** 最大的阻塞队列头重试值;至少为1;最大为1^16*/
    private static final int HEAD_SPINS = (NCPU > 1) ? 1 << 10 : 1;

    /** 最大的在重新阻塞时重试值 */
    private static final int MAX_HEAD_SPINS = (NCPU > 1) ? 1 << 16 : 1;

    /** 当等待的自旋锁栈溢出时主要的yielding */
    private static final int OVERFLOW_YIELD_RATE = 7; // must be power 2 - 1

    /**被自旋读卡器栈溢出前所使用的字节数 */
    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 << LG_READERS;
    private static final long RBITS = WBIT - 1L;
    private static final long RFULL = RBITS - 1L;
    private static final long ABITS = RBITS | WBIT;
    private static final long SBITS = ~RBITS; // note overlap with ABITS

    /*
     * 通过测试有三种stamp的模式能够被分辨出来 
     * (m = stamp & ABITS):write mode: m == WBIT
     * 乐观锁的模式: m == 0L (甚至乐观锁被持有的时候)
     * 读模式: m > 0L && m <= RFULL (stamp是state的复制 也就是说这里的stamp是用来计算的 而state是用来获取值的
     * 但是read在stamp中持有的数量是未被用于其他确认模式的
     *
     * 这个轻微的差别在于state的解码:
     * (state & ABITS) == 0L 显示该锁当前是未上锁的
     * (state & ABITS) == RBITS 一个特殊的显示值
     * 指示自旋锁定以操纵读卡器位溢出
     */

    /** 初始化state的初始值;避免0失效值. */
    private static final long ORIGIN = WBIT << 1;

    // 来自取消需求方法的特殊值 一边响应者可以返回IE(ioexception)
    private static final long INTERRUPTED = 1L;

    // 状态标记的值 
    private static final int WAITING   = -1;
    private static final int CANCELLED =  1;

    // 标记的模式  是int类型而不是boolean类型去允许计算的
    private static final int RMODE = 0;
    private static final int WMODE = 1;

    /** 等待点 */
    static final class WNode {
        volatile WNode prev;
        volatile WNode next;
        volatile WNode cowait;    // 连接读者列表
        volatile Thread thread;   // 当可能停止时不为零
        volatile int status;      // 当0时 暂停或者取消
        final int mode;           // RMODE / WMODE
        WNode(int m, WNode p) { mode = m; prev = p; }
    }

    /** clh的队列头 */
    private transient volatile WNode whead;
    /** 队列尾部 */
    private transient volatile WNode wtail;

    // views
    transient ReadLockView readLockView;
    transient WriteLockView writeLockView;
    transient ReadWriteLockView readWriteLockView;

    /** 锁队列状态 */
    private transient volatile long state;
    /** state读取计数饱和时的额外读卡器计数 */
    private transient int readerOverflow;

重要的中间方法1:比较替换

private boolean casState(long expectedValue, long newValue) {
        return STATE.compareAndSet(this, expectedValue, newValue);
    }

重要的中间方法

二、1.写锁代码以及源码解析

官方示例代码 ----提供大概的理解以及应用

/**
 * 类说明:JDK1.8源码自带的示例
 */
public class StampedLockDemo {
    //一个点的x,y坐标
    private double x,y;
    /**Stamped类似一个时间戳的作用,每次写的时候对其+1来改变被 *** 作对象的Stamped值
     * 这样其它线程读的时候发现目标对象的Stamped改变,则执行重读*/
    private final StampedLock sl = new StampedLock();

    //【写锁(排它锁)】
    void move(double deltaX,double deltaY) {// an exclusively locked method
        /**stampedLock调用writeLock和unlockWrite时候都会导致stampedLock的stamp值的变化
         * 即每次+1,直到加到最大值,然后从0重新开始*/
        long stamp =sl.writeLock(); //写锁
        try {
            x +=deltaX;
            y +=deltaY;
        } finally {
            sl.unlockWrite(stamp);//释放写锁
        }
    }

当然重要的还是要理解源码底层的意思

acquireWrite代码部分解析

 private long acquireWrite(boolean interruptible, long deadline) {
        WNode node = null, p;
        for (int spins = -1;;) { // spin while enqueuing
            long m, s, ns;
            if ((m = (s = state) & ABITS) == 0L) {
                if ((ns = tryWriteLock(s)) != 0L)
                    return ns;
            }
            else if (spins < 0)
                spins = (m == WBIT && wtail == whead) ? SPINS : 0;
            else if (spins > 0) {
                --spins;
                Thread.onSpinWait();
            }
            else if ((p = wtail) == null) { // initialize queue
                WNode hd = new WNode(WMODE, null);
                if (WHEAD.weakCompareAndSet(this, null, hd))
                    wtail = hd;
            }
            else if (node == null)
                node = new WNode(WMODE, p);
            else if (node.prev != p)
                node.prev = p;
            else if (WTAIL.weakCompareAndSet(this, p, node)) {
                p.next = node;
                break;
            }
        }
        ..........

这里的代码(jdk9)相比与jdk8代码相差比较大
使用的是系统nanotime进行对deadlie进行设置 当超过这个时间 读 *** 作还没有进行完成就会对读 *** 作打断 转而进行写 *** 作的判断

jdk8相关代码


这里是使next值大于Rfull 这样就会跳进一个写 *** 作 而当大于rFull时 所有的读 *** 作都会出错 造成没办法进行读 转而进行写 *** 作

2.读锁代码

代码如下(示例):

//【乐观读锁】
    double distanceFromOrigin() { // A read-only method
        /**
         * tryOptimisticRead是一个乐观的读,使用这种锁的读不阻塞写
         * 每次读的时候得到一个当前的stamp值(类似时间戳的作用)
         */
        long stamp = sl.tryOptimisticRead();
        //这里就是读 *** 作,读取x和y,因为读取x时,y可能被写了新的值,所以下面需要判断
        double currentX = x, currentY = y;
        /**如果读取的时候发生了写,则stampedLock的stamp属性值会变化,此时需要重读,
         * validate():比较当前stamp和获取乐观锁得到的stamp比较,不一致则失败。
         * 再重读的时候需要加读锁(并且重读时使用的应当是悲观的读锁,即阻塞写的读锁)
         * 当然重读的时候还可以使用tryOptimisticRead,此时需要结合循环了,即类似CAS方式
         * 读锁又重新返回一个stampe值*/
        if (!sl.validate(stamp)) {//如果验证失败(读之前已发生写)
            stamp = sl.readLock(); //悲观读锁
            try {
                currentX = x;
                currentY = y;
            }finally{
                sl.unlockRead(stamp);//释放读锁
            }
        }
        //读锁验证成功后执行计算,即读的时候没有发生写
        return Math.sqrt(currentX *currentX + currentY *currentY);
    }
读锁源码部分解析

读锁的重要 *** 作

private long acquireRead(boolean interruptible, long deadline) {
        boolean wasInterrupted = false;
        WNode node = null, p;
        for (int spins = -1;;) {
            WNode h;
            if ((h = whead) == (p = wtail)) {
                for (long m, s, ns;;) {
                    if ((m = (s = state) & ABITS) < RFULL ? 
                        casState(s, ns = s + RUNIT) :
                        (m < WBIT && (ns = tryIncReaderOverflow(s)) != 0L)) {
                        if (wasInterrupted)
                            Thread.currentThread().interrupt();
                        return ns;
                    }
                    else if (m >= WBIT) {
                        if (spins > 0) {
                            --spins;
                            Thread.onSpinWait();
                        }
                        else {
                            if (spins == 0) {
                                WNode nh = whead, np = wtail;
                                if ((nh == h && np == p) || (h = nh) != (p = np))
                                    break;
                            }
                            spins = SPINS;
                        }
                    }
                }
            }

其中的 if ((m = (s = state) & ABITS) < RFULL ? 模块中
当m>WBIT 时有两种情况
情况一自旋(spins)>0 即可理解为 当写线程出发的时候 会打断写线程 继续重新尝试读线程
情况二 自旋(spins)==0 即写线程重试不允许了 则退出读 执行写

读锁 *** 作

public long readLock() {
        long s, next;
        // 通过 acquiredRead 方法因对常见的未认证的情况
        //执行条件
        return (whead == wtail
                && ((s = state) & ABITS) < RFULL
                && casState(s, next = s + RUNIT))
            ? next
            : acquireRead(false, 0L);
            
    }

读 *** 作时: casState(s, next = s + RUNIT))//读 *** 作时该处+1L,来进行casState状态比较 ——如若确定为读 *** 作的锁 ,则state值会小于rfull 则返回一个相同的s值此时stamp值不变

读锁的尝试获取 *** 作

public long tryReadLock() {
        long s, m, next;
        while ((m = (s = state) & ABITS) != WBIT) {
            if (m < RFULL) {
                if (casState(s, next = s + RUNIT))
                    return next;
            }
            else if ((next = tryIncReaderOverflow(s)) != 0L)
                return next;
        }
        return 0L;
    }
3.读锁升级代码
//读锁升级为写锁
    void moveIfAtOrigin(double newX, double newY) { // upgrade
        // 读锁(这里可用乐观锁替代)
        long stamp = sl.readLock();
        try {
            //循环,检查当前状态是否符合
            while (x == 0.0 && y == 0.0) {
                long ws = sl.tryConvertToWriteLock(stamp);
                //如果写锁成功
                if (ws != 0L) {
                    stamp = ws;// 替换stamp为写锁戳
                    x = newX;//修改数据
                    y = newY;
                    break;
                }
                //转换为写锁失败
                else {
                    //释放读锁
                    sl.unlockRead(stamp);
                    //获取写锁(必要情况下阻塞一直到获取写锁成功)
                    stamp = sl.writeLock();
                }
            }
        } finally {
            //释放锁(可能是读/写锁)
            sl.unlock(stamp);
        }
    }
}

4.核心方法 总结

提示:这里对文章进行总结:

例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://www.outofmemory.cn/langs/741625.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-28
下一篇 2022-04-28

发表评论

登录后才能评论

评论列表(0条)

保存