• 线程基础知识15StampedLock


    1 简介

      StampedLock是JDK1.8中新增的一个读写锁,也是对JDK1.5中的读写锁ReentrantReadWriteLock的优化。在原先读写锁的基础上新增了一种叫乐观读(Optimistic Reading)的模式。该模式并不会加锁,所以不会阻塞线程,会有更高的吞吐量和更高的性能。

      ReentrantReadWriteLock介绍 :https://www.cnblogs.com/jthr/p/16179865.html

     

    2 特性及注意事项

    2.1 三种访问数据模式

      1)Writing(独占写锁):writeLock 方法会使线程阻塞等待独占访问,同一时刻有且只有一个写线程获取锁资源,功能和ReentrantReadWriteLock 的读类似,

      2)Reading(悲观读锁):readLock方法,读读共享,读写互斥。功能和ReentrantReadWriteLock的写锁类似

      3)Optimistic Reading(乐观读):Optimistic reading(乐观读模式):无锁机制,类似于数据库中的乐观锁,支持读写并发,很乐观认为读取时没人修改,假如被修改再实现升级为悲观读模式。

     

    2.2 支持读写锁相互转换

      ReentrantReadWriteLock 当线程获取写锁后可以降级成读锁,但是反过来则不行。

      StampedLock提供了读锁和写锁相互转换的功能,使得该类支持更多的应用场景。

     

    2.3 注意事项

      1)StampedLock是不可重入的,危险(如果一个线程已经持有了写锁,再去获取写锁的话就会造成死锁)

      2)StampedLock 的悲观读锁和写锁都不支持条件变量(Condition),这个也需要注意。

      3)所有获取锁的方法,都返回一个邮戳(Stamp),Stamp为零表示获取失败,其余都表示成功;

      4)所有释放锁的方法,都需要一个邮戳(Stamp),这个Stamp必须是和成功获取锁时得到的Stamp一致;

      5)使用 StampedLock一定不要调用中断操作,即不要调用interrupt() 方法,如果需要支持中断功能,一定使用可中断的悲观读锁 readLockInterruptibly()和写锁writeLockInterruptibly()

     

    StampedLockReentrantReadWriteLock效率高

      关键在于StampedLock 提供的乐观读,我们知道ReentrantReadWriteLock 支持多个线程同时获取读锁,但是当多个线程同时读的时候,所有的写线程都是阻塞的。

      StampedLock 的乐观读允许一个写线程获取写锁,所以不会导致所有写线程阻塞,也就是当读多写少的时候,写线程有机会获取写锁,减少了线程饥饿的问题,吞吐量大大提高。同时允许多个乐观读和一个先线程同时进入临界资源操作,那读取的数据可能是错的怎么办?

      是的,乐观读不能保证读取到的数据是最新的,所以将数据读取到局部变量的时候需要通过 lock.validate(stamp) 校验下是否被写线程修改过,若是修改过则需要上悲观读锁,再重新读取数据到局部变量。

      同时由于乐观读并不是锁,所以没有线程唤醒与阻塞导致的上下文切换,性能更好。

     

    4 示例

    public class StanpedlockLockTest1 {
    
        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 (true) { 
                    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); //释放读锁或写锁
            }
        }
    }

    5 使用场景

      对于读多写少的高并发场景 StampedLock的性能很好,通过乐观读模式很好的解决了写线程“饥饿”的问题,我们可以使用StampedLock 来代替ReentrantReadWriteLock


     

  • 相关阅读:
    LeetCode First Bad Version (二分查找)
    LeetCode Permutations II (全排列)
    LeetCode Permutations (全排列)
    LeetCode Minimum Path Sum (简单DP)
    LeetCode Binary Tree Postorder Traversal(数据结构)
    LeetCode Sort Colors (技巧)
    邹忌
    参数传递
    父子窗体返回值与互操作
    其他数据库连接
  • 原文地址:https://www.cnblogs.com/jthr/p/16206485.html
Copyright © 2020-2023  润新知