美文网首页
6、ReentrantReadWriteLock

6、ReentrantReadWriteLock

作者: 神秘空指针 | 来源:发表于2018-09-29 15:02 被阅读0次

    前言

    相比于ReentrantLock 互斥的设计,现实情况是我们更多的碰到的是 读的次数远远大于写的次数。如果在一个读场景远大于写场景的情况下,我们再去使用ReentrantLock 显得浪费资源。这里介绍ReentrantReadWriteLock(读写锁) 就是为了解决这个问题。

    1、ReentrantReadWriteLock 用法

    ReentrantReadWriteLock 分为读锁和写锁。

    所谓的读锁 其实是就是AQS 中的共享模式,允许多个线程同时持有共享锁。而写锁则是AQS 中的独占模式,只有一个线程能够获取锁。
    前面文章介绍过AQS ,对了共享和独占模式应该有所了解。

    那么这里有一个问题,我们之前说过AQS 中维护了一个state,但是在ReentrantReadWriteLock如何同时保存写锁和读锁的状态的?
    答案是 ReentrantReadWriteLock 将state 分成了 高16位 和 低16位。高16代表的是读锁的状态,所以共享锁的线程不会超过 2 << 16,
    低16位是独占锁的状态。

    (1)读锁和写锁的获取条件

    1、写锁 同ReentrantLock 。无其他线程 持有写锁,没有线程持有读锁
    2、读锁 没有其他线程持有写锁并且持有读锁的线程不超过 (2<<16) - 1 原因见上面

    (2)读写锁的锁降级

    因为写锁是独占锁,相比于读锁永远更高的权限。我们认为从写锁到读锁是一种锁的降级。什么情况会发生锁得降级呢,我们来分析一下。
    1、读锁要想获取写锁是不可能的,因为写锁是和一切读锁互斥(包括自己拥有的)。
    2、写锁可以获取读锁,见上面写锁获取条件

    综上,根据读锁和写锁获取的条件 读写锁只能降级 无法升级。下面实验一下

    锁升级

     public static void main(String[] args) {
    
            ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
          
            lock.readLock().lock();
            System.out.println("小米:我这有一个读锁");
    
            lock.writeLock().lock();
            System.out.println("小米:我从读锁中拿到一个读锁");
    
            lock.writeLock().unlock();
            System.out.println("小米:写锁解开吧!!!");
    
            lock.readLock().unlock();
            System.out.println("小米:我也不要读锁了。给你吧");
        }
    

    输出结果


    image.png

    可见在拥有了读锁的情况下,线程时拿不到写锁的。

    下面我们读写锁互换位置试试

    锁降级

        public static void main(String[] args) {
    
            ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
            lock.writeLock().lock();
            System.out.println("小米:我从读锁中拿到一个读锁");
    
            lock.readLock().lock();
            System.out.println("小米:我这有一个读锁");
    
    
            lock.writeLock().unlock();
            System.out.println("小米:写锁解开吧!!!");
    
            lock.readLock().unlock();
            System.out.println("小米:我也不要读锁了。给你吧");
    
        }
    

    输出结果


    .

    从输出结果来看,先获取写锁再获取读锁是没有问题的。

    再看看一个正常使用的例子

    public static void main(String[] args) {
    
            ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    
            new Thread(() -> {
                lock.readLock().lock();
                System.out.println("小米:我这有一个读锁");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                System.out.println("时间过去了三秒");
                System.out.println("小米:我不要读锁了。给你吧");
                lock.readLock().unlock();
            }).start();
    
            new Thread(() -> {
                System.out.println("小明:我想要一个写锁");
                lock.writeLock().lock();
                System.out.println("小明:嘻嘻,我获取了一个写锁");
                lock.writeLock().unlock();
            }).start();
        }
    

    一个线程持有读锁三秒,一个线程等待读锁
    输出结果


    image.png

    到这里ReentrantReadWriteLock 已经结束了,还有什么疑问吗?

    相关文章

      网友评论

          本文标题:6、ReentrantReadWriteLock

          本文链接:https://www.haomeiwen.com/subject/obbpoftx.html