展开
拓展 关闭
订阅号推广码
GitHub
视频
公告栏 关闭

锁(十):StampedLock原理及使用

  • 简介
1、一般应用,都是读多写少,ReentrantReadWriteLock 因读写互斥,故读时阻塞写,因而性能上上不去。可能会使写线程饥饿
2、StampedLock的特点
    所有获取锁的方法,都返回一个邮戳(Stamp),Stamp为0表示获取失败,其余都表示成功; 
    所有释放锁的方法,都需要一个邮戳(Stamp),这个Stamp必须是和成功获取锁时得到的Stamp一致; 
    StampedLock是不可重入的;(如果一个线程已经持有了写锁,再去获取写锁的话就会造成死锁) 支持锁升级跟锁降级 
    可以乐观读也可以悲观读 
    使用有限次自旋,增加锁获得的几率,避免上下文切换带来的开销 
    乐观读不阻塞写操作,悲观读,阻塞写得操作
3、StampedLock的优点
    相比于ReentrantReadWriteLock,吞吐量大幅提升
4、StampedLock的缺点
    api相对复杂,容易用错 内部实现相比于ReentrantReadWriteLock复杂得多
5、StampedLock的原理
    每次获取锁的时候,都会返回一个邮戳(stamp),相当于mysql里的version字段 释放锁的时候,再根据之前的获得的邮戳,去进行锁释放
6、使用stampedLock注意点
    如果使用乐观读,一定要判断返回的邮戳是否是一开始获得到的,如果不是,要去获取悲观读锁,再次去读取
  • 案例1
public class StampedLockDemo {
    // 成员变量
    private double x, y;

    // 锁实例,获取StampedLock
    private final StampedLock sl = new StampedLock();

    // 排它锁-写锁(writeLock)
    void move(double deltaX, double deltaY) {
        // 打开锁
        long stamp = sl.writeLock();
        // 执行操作
        try {
            x += deltaX;
            y += deltaY;
        } finally {
            // 关系锁
            sl.unlockWrite(stamp);
        }
    }

    // 乐观读锁
    double distanceFromOrigin() {
        // 尝试获取乐观读锁(1)
        long stamp = sl.tryOptimisticRead();
        // 将全部变量拷贝到方法体栈内(2)
        double currentX = x, currentY = y;
        // 检查在(1)获取到读锁票据后,锁有没被其他写线程排它性抢占(3)
        if (!sl.validate(stamp)) {      // 如果验证失败,就去获取悲观读,即第4步
            // 如果被抢占则获取一个共享读锁(悲观获取)(4)
            stamp = sl.readLock();
            try {
                // 将全部变量拷贝到方法体栈内(5)
                currentX = x;
                currentY = y;
            } finally {
                // 释放共享读锁(6)
                sl.unlockRead(stamp);
            }
        }
        // 返回计算结果(7)
        return Math.sqrt(currentX * currentX + currentY * currentY);
    }

    // 使用悲观锁获取读锁,并尝试转换为写锁
    void moveIfAtOrigin(double newX, double newY) {
        // 这里可以使用乐观读锁替换(1)
        long stamp = sl.readLock(); // 获取悲观读锁
        try {
            // 如果当前点在原点则移动(2)
            while (x == 0.0 && y == 0.0) {
                // 尝试将获取的读锁升级为写锁(3)
                long ws = sl.tryConvertToWriteLock(stamp);
                // 升级成功,则更新票据,并设置坐标值,然后退出循环(4)
                if (ws != 0L) {
                    stamp = ws;
                    x = newX;
                    y = newY;
                    break;
                } else {
                    // 读锁升级写锁失败则释放读锁,显示获取独占写锁,然后循环重试(5)
                    sl.unlockRead(stamp);
                    stamp = sl.writeLock();
                }
            }
        } finally {
            // 释放锁(6)
            sl.unlock(stamp);
        }
    }

}
posted @ 2022-05-14 17:09  DogLeftover  阅读(91)  评论(0编辑  收藏  举报