StampedLock

为什么要引入StampedLcok

  重入锁、读写锁、StampedLock的并发度对比。:
在这里插入图片描述
  StampedLock读和写不互斥,并发度更高。StampedLock用乐观读提高并发度。
  基本用法:

public class Point {
    private double x, y;
    private final StampedLock stampedLock = new StampedLock();

    void move(double deltaX, double deltaY){ // 修改值的函数,并发情况下多个线程进行调用
        long stamp = stampedLock.writeLock(); // 获取写锁
        try {
            x = deltaX;
            y = deltaY;
        }finally {
            stampedLock.unlockWrite(stamp); // 释放写锁
        }
    }

    double distanceFromOrigin(){
        long stamp = stampedLock.tryOptimisticRead(); // 乐观读,得到一个版本号
        //取得当前值
        double currentX = x;
        double currentY = y;

        if(!stampedLock.validate(stamp)){ // 用validate检查版本号是否过期,即是否有线程修改数据
            stamp = stampedLock.readLock(); // 过期则获取读锁
            try {
                //重新取得值
                currentX = x;
                currentY = y;
            }
            finally {
                stampedLock.unlockRead(stamp); // 释放读锁
            }
        }
        return Math.sqrt(currentX * currentX + currentY * currentY);
    }
}

乐观读原理

  和读写锁一样,StampedLock也有一个state,而且包含了数据的版本号,因为CAS操作只能操作一个变量。

public class StampedLock implements java.io.Serializable {
	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; // 读锁状态取反
    
    // 锁的初始化状态
	private static final long ORIGIN = WBIT << 1;
	
	private transient volatile long state; // 状态值
	// 构造函数设置初始化的状态值
	public StampedLock() {
        state = ORIGIN;
    }
}

  为什么将state值设为ORIGIN的值,而不是0
  原因在于乐观读的实现:

public long tryOptimisticRead() {
    long s;
    return (((s = state) & WBIT) == 0L) ? (s & SBITS) : 0L;// 如果有线程持有写锁返回0
}
public boolean validate(long stamp) {
    U.loadFence();
    return (stamp & SBITS) == (state & SBITS); // 如果stamp为0返回false,因为state第九位为1,state & SBITS不为0
}

  当(s = state) & WBIT != 0时(也就是有线程持有写锁时),tryOptimisticRead() 返回0,在调用validate(0)时,会返回false。也就是说当有线程持有写锁后,即使释放了写锁,也认为数据被修改(state低第八位为0,但是低第九位为1,两个数&SBITS的结果不同),所以validate返回false。
  而如果将state初始为0的话,当一个读线程在调用validate前,持有写锁的线程释放了写锁,validate会返回true(因为stamp和state均为0),此时不符合逻辑。

  为什么vaildate比较(stamp & SBITS) == (state & SBITS),而不是比较stamp和state:因为读锁和读锁之间不互斥,当state和stamp低七位不同时,仍然返回true

  在validate中用了U.loadFence()内存屏障,因为stamp和SBITS不是volatile的,用内存屏障禁止和前面的取值操作重排序。

悲观读写:

  StampedLock实现悲观读写不是基于AQS,而是重新实现了一个阻塞队列,内部有一个静态内部类表示阻塞线程节点。

static final class WNode {
    volatile WNode prev;
    volatile WNode next;
    volatile WNode cowait;
    volatile Thread thread;
    volatile int status;// 取值为0、WARNING、CANCELLED
    final int mode;// 取值为RMODE或WMODE
    WNode(int m, WNode p) { mode = m; prev = p; }
}

    private transient volatile WNode whead;
    
    private transient volatile WNode wtail;

  阻塞队列实现类似于AQS,初始时,头结点和尾结点为NULL,然后创建空节点往队列的尾部加入线程节点。不同在于,AQS中CAS修改线程状态失败后,会将节点加入阻塞队列,而该队列是进行自旋,自旋一定次数仍取不到锁再进入阻塞。自旋的次数由CPU核心数决定:

private static final int NCPU = Runtime.getRuntime().availableProcessors();

private static final int SPINS = (NCPU > 1) ? 1 << 6 : 0;

  以写锁为例查看自旋的实现:

public long writeLock() {
    long s, next;
    return ((((s = state) & ABITS) == 0L &&
             U.compareAndSwapLong(this, STATE, s, next = s + WBIT)) ?
            next : acquireWrite(false, 0L));
}

  只有没有线程持有锁(读锁和写锁)才能修改state值((s = state) & ABITS) == 0L)。若操作失败进入阻塞队列,并自旋。

private static final int SPINS = (NCPU > 1) ? 1 << 6 : 0;

    // 进入队列前最大重试次数
    private static final int HEAD_SPINS = (NCPU > 1) ? 1 << 10 : 0;

    // 在队头阻塞前的最大重试次数
    private static final int MAX_HEAD_SPINS = (NCPU > 1) ? 1 << 16 : 0;

    /** The period for yielding when waiting for overflow spinlock */
    private static final int OVERFLOW_YIELD_RATE = 7; // must be power 2 - 1

    /** The number of bits to use for reader count before overflowing */
    private static final int LG_READERS = 7;

private long acquireWrite(boolean interruptible, long deadline) {
        WNode node = null, p;
        for (int spins = -1;;) { // 入队自旋
            long m, s, ns;
            if ((m = (s = state) & ABITS) == 0L) {
                if (U.compareAndSwapLong(this, STATE, s, ns = s + WBIT))
                    return ns; // 自旋时拿到锁,函数返回
            }
            else if (spins < 0)
                spins = (m == WBIT && wtail == whead) ? SPINS : 0;
            else if (spins > 0) {
                if (LockSupport.nextSecondarySeed() >= 0) // 自旋,在一定概率下将spins递减
                    --spins;
            }
            else if ((p = wtail) == null) { // 初始化队列
                WNode hd = new WNode(WMODE, null);
                if (U.compareAndSwapObject(this, WHEAD, null, hd))
                    wtail = hd;
            }
            else if (node == null)
                node = new WNode(WMODE, p);
            else if (node.prev != p)
                node.prev = p;
            else if (U.compareAndSwapObject(this, WTAIL, p, node)) {
                p.next = node;
                break; // CAS成功将线程加入队列尾部退出循环
            }
        }

        for (int spins = -1;;) {
            WNode h, np, pp; int ps;
            if ((h = whead) == p) {
                if (spins < 0)
                    spins = HEAD_SPINS;
                else if (spins < MAX_HEAD_SPINS)
                    spins <<= 1;
                for (int k = spins;;) { // 队头重试
                    long s, ns;
                    if (((s = state) & ABITS) == 0L) { // 再次尝试获取锁
                        if (U.compareAndSwapLong(this, STATE, s,
                                                 ns = s + WBIT)) {
                            whead = node;
                            node.prev = null;
                            return ns;
                        }
                    }
                    else if (LockSupport.nextSecondarySeed() >= 0 &&
                             --k <= 0) // 自旋
                        break;
                }
            }
            else if (h != null) { // 帮助释放旧的等待线程
                WNode c; Thread w;
                while ((c = h.cowait) != null) { // 结束阻塞,然后唤醒cowaiter在的reader线程
                    if (U.compareAndSwapObject(h, WCOWAIT, c, c.cowait) &&
                        (w = c.thread) != null)
                        U.unpark(w);
                }
            }
            if (whead == h) {
                if ((np = node.prev) != p) {
                    if (np != null)
                        (p = np).next = node;   // stale
                }
                else if ((ps = p.status) == 0)
                    U.compareAndSwapInt(p, WSTATUS, 0, WAITING);
                else if (ps == CANCELLED) {
                    if ((pp = p.prev) != null) {
                        node.prev = pp;
                        pp.next = node;
                    }
                }
                else {
                    long time; // 参数为0表示未超时
                    if (deadline == 0L)
                        time = 0L;
                    else if ((time = deadline - System.nanoTime()) <= 0L)
                        return cancelWaiter(node, node, false);
                    Thread wt = Thread.currentThread();
                    U.putObject(wt, PARKBLOCKER, this);
                    node.thread = wt;
                    if (p.status < 0 && (p != h || (state & ABITS) != 0L) &&
                        whead == h && node.prev == p)
                        U.park(false, time);  // 进入阻塞
                    node.thread = null;
                    U.putObject(wt, PARKBLOCKER, null);
                    if (interruptible && Thread.interrupted())
                        return cancelWaiter(node, node, true);
                }
            }
        }
    }

  第一个大循环里,一边尝试获得锁(如果获取到锁就返回),一边把线程Node放到队列尾部,一直自旋到节点放到队列尾部。
  在第二个循环里,如果当前线程在队列头部,则进行新的一轮自旋,次数上限为MAX_HEAD_SPINS,如果获得了锁则返回,否则阻塞。
  当cowaiter的头部线程被唤醒时,会顺带唤醒所有读线程。

  释放锁的操作:

public void unlockWrite(long stamp) {
        WNode h;
        if (state != stamp || (stamp & WBIT) == 0L) // 锁版本不正确或没有线程获取到锁
            throw new IllegalMonitorStateException();
        state = (stamp += WBIT) == 0L ? ORIGIN : stamp; // 将state归位
        if ((h = whead) != null && h.status != 0) // 唤醒队头节点
            release(h);
    }

private void release(WNode h) {
        if (h != null) {
            WNode q; Thread w;
            U.compareAndSwapInt(h, WSTATUS, WAITING, 0);
            if ((q = h.next) == null || q.status == CANCELLED) {
                for (WNode t = wtail; t != null && t != h; t = t.prev) // 往前寻找头结点
                    if (t.status <= 0)
                        q = t;
            }
            if (q != null && (w = q.thread) != null) // 唤醒线程
                U.unpark(w);
        }
    }

参考资料:《Java并发实现原理:JDK源码剖析》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值