• 【Java并发】聊聊ReentrantReadWriteLock锁降级和StampedLock邮戳锁


    面试题
    1.你说你用过读写锁,锁饥饿问题是什么?
    2.有没有比读写锁更快的锁?
    3.StampedLock知道吗?(邮戳锁/票据锁)
    4.ReentrantReadWriteLock有锁降级机制策略你知道吗?

    并发编程领域,有多线程进行提升整体性能,但是却引入了共享数据安全性问题。基本就是无锁编程下的单线程操作,有互斥同步锁操作,但是性能不高,并且同一时刻只有一个线程可以操作资源类。但是对于大多数常见下,都是读操作多,写操作少,那么可以利用将锁的粒度进行细化,进而分化出读锁/写锁。也就是syn/ReentrantLock的升级版本ReentrantReadWriteLock。

    读写锁

    在这里插入图片描述

    public class LockDemo {
    
        private static Map<Integer,Integer> cacheMap = new HashMap<>();
        private Lock lock = new ReentrantLock();
        private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    
    
        public void write(Integer key, Integer value) {
            readWriteLock.writeLock().lock();
            try {
                System.out.println("当前"+key+"正在写入");
                Thread.sleep(500);
                cacheMap.put(key,value);
                System.out.println("当前"+key+"写入完毕");
            } catch (Exception e) {
                e.fillInStackTrace();
            } finally {
                readWriteLock.writeLock().unlock();
            }
        }
    
        public void read(Integer key) {
            readWriteLock.readLock().lock();
            try {
                System.out.println("当前"+key+"正在读取");
                cacheMap.get(key);
                System.out.println("当前"+key+"读取完毕");
            } catch (Exception e) {
                e.fillInStackTrace();
            } finally {
                readWriteLock.readLock().unlock();
            }
        }
    
    
        public static void main(String[] args) {
            LockDemo lockDemo  = new LockDemo();
    
            for (int i = 0; i < 10; i++) {
                int finalI = i;
                new Thread(()->{
                    lockDemo.write(finalI, finalI);
                }).start();
            }
    
            for (int i = 0; i < 10; i++) {
                int finalI = i;
                new Thread(()->{
                    lockDemo.read(finalI);
                }).start();
            }
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    在这里插入图片描述
    从执行结果来看,读锁不互斥。读取1的时候,还可以读取别的数据。

    锁降级

    锁降级是为了让当前线程感知到数据的变化,目的是保证数据可见性
    在这里插入图片描述
    在这里插入图片描述

    public class LockDemo2 {
    
        public static void main(String[] args) {
            ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
            ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
            ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
    
            readLock.lock();
            System.out.println("读取数据");
            readLock.unlock();
            writeLock.lock();
            System.out.println("写入数据");
            readLock.lock();
            System.out.println("读取数据");
            writeLock.unlock();
            readLock.unlock();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    调整顺序之后,读锁不能升级为写锁,但是写锁可以降级为读锁。
    在这里插入图片描述

    存在的问题

    在这里插入图片描述
    为了解决读写锁,锁饥饿的问题,解决方案有两个,1.通过使用公平锁来解决,但是公平锁会牺牲系统吞吐量为代价的。
    2.使用stampedLock邮戳锁。
    在这里插入图片描述

    stampedlock

    代表了锁的状态。当stamp返回零时,表示线程获取锁失败。并且,当释放锁或者转换锁的时候,都要传入最初获取的stamp值。

    因为读写lock,虽然可以提升一定的性能,但是因为存在饥饿的问题,读写互斥。而邮戳锁是一种乐观锁,使用类似版本校验的机制,选判断数据有没有修改,没有修改直接读取,有修改则升级为悲观读取。其实是一种权衡。

    StampedLock有三种访问模式
    ①Reading(读模式):功能和ReentrantReadWriteLock的读锁类似
    ②Writing(写模式):功能和ReentrantReadWriteLock的写锁类似
    ③**Optimistic reading(乐观读模式):无锁机制,类似于数据库中的乐观锁,**支持读写并发,很乐观认为读取时没人修改,假如被修改再实现升级为悲观读模式

    StampedLock的缺点

    • StampedLock 不支持重入,没有Re开头
    • StampedLock 的悲观读锁和写锁都不支持条件变量(Condition),这个也需要注意。
    • 使用 StampedLock一定不要调用中断操作,即不要调用interrupt() 方法
      • 如果需要支持中断功能,一定使用可中断的悲观读锁 readLockInterruptibly()和写锁writeLockInterruptibly()

    小结

    本篇主要介绍了读写锁,以及读写锁的锁饥饿问题,为了进一步提升性能引入了邮戳锁,但是邮戳锁不支持重入和中断等。

  • 相关阅读:
    Postgresql运维信息(一)
    Himall商城类型帮助类将string类型转换成Bool类型
    时域卷积定理&频域卷积定理
    Aspect 切入点 @Pointcut 语法详解
    多线程学习(C/C++)
    基于单片机的病床呼叫系统设计研究
    华封科技举办2022年首场技术认证培训
    设计一个支持多版本的APP的后端服务
    ES2015+ 备忘清单
    大数据毕业设计选题推荐-污水处理大数据平台-Hadoop-Spark-Hive
  • 原文地址:https://blog.csdn.net/jia970426/article/details/132788752