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

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

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

读写锁

【Java并发】聊聊ReentrantReadWriteLock锁降级和StampedLock邮戳锁_第1张图片

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();
        }
    }

}

【Java并发】聊聊ReentrantReadWriteLock锁降级和StampedLock邮戳锁_第2张图片
从执行结果来看,读锁不互斥。读取1的时候,还可以读取别的数据。

锁降级

锁降级是为了让当前线程感知到数据的变化,目的是保证数据可见性
【Java并发】聊聊ReentrantReadWriteLock锁降级和StampedLock邮戳锁_第3张图片
【Java并发】聊聊ReentrantReadWriteLock锁降级和StampedLock邮戳锁_第4张图片

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();
    }

}

调整顺序之后,读锁不能升级为写锁,但是写锁可以降级为读锁。
【Java并发】聊聊ReentrantReadWriteLock锁降级和StampedLock邮戳锁_第5张图片

存在的问题

【Java并发】聊聊ReentrantReadWriteLock锁降级和StampedLock邮戳锁_第6张图片
为了解决读写锁,锁饥饿的问题,解决方案有两个,1.通过使用公平锁来解决,但是公平锁会牺牲系统吞吐量为代价的。
2.使用stampedLock邮戳锁。
【Java并发】聊聊ReentrantReadWriteLock锁降级和StampedLock邮戳锁_第7张图片

stampedlock

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

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

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

StampedLock的缺点

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

小结

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

你可能感兴趣的:(#,并发编程,java,开发语言)