• ReentrantReadWriteLock


    ReentrantReadWriteLock, 可重入读写锁, 包含读锁与写锁,具体结构如下图:

    ReentrantReadWriteLock包含了很多内部类,其中最核心的为Sync、ReadLock、WriteLock

    Sync内部类

    sync内部类是AQS的实现类,实现了共享锁、独占锁的获取与释放方法,同时将AQS中的state状态值拆分为高16位、低16位(分别代表读锁(共享锁)获取数量、写锁(独占锁)获取数量)

    /** int 类型state变量拆分为高16位,,代表共享模式; 低16位,代表独占模式**/
    static final int SHARED_SHIFT = 16; static final int SHARED_UNIT = (1 << SHARED_SHIFT); static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; /** 返回共享锁的获取次数(包含重入), 左移16位,低于16位返回0 */ static int sharedCount(int c) { return c >>> SHARED_SHIFT; } /** 返回独占锁的重入次数(独占不重入就为1),c直接执行与操作(就是c本身) */ static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

    sync内部类中包含了HoldCounter,ThreadLocalHoldCounter两个静态内部类以及readHolds, cachedHoldCounter,firstReader,firstReaderHoldCount四个不被序列化的变量

    /** 记录每个线程持有的读锁数量  */
    static final class HoldCounter {
        int count = 0;
        // Use id, not reference, to avoid garbage retention
        final long tid = getThreadId(Thread.currentThread());
    }

    readHolds : 记录当前线程可重入读锁的数量,仅在Sync构造函数以及readObjec方法中初始化,当前线程的可重入读锁数量降为0时删除(ThreadLocalHoldCounter ThreadLocl子类)
    cachedHoldCounter : 记录最后一个线程获取读锁的数量
    firstReader : 记录第一个获取读锁的线程
    firstReaderHoldCount : 记录第一个线程获取读锁的次数

    ReadLock内部类、WriteLock内部类

    ReadLock(读锁)、WriteLock(写锁),都实现了Lock接口

    锁获取

    下图是关于读锁,写锁的获取过程,红色代表读锁,蓝色代表写锁

    读锁

    读锁(共享锁)的获取,调用的是AQS中的acquireShard方法

    1.尝试获取共享锁,即判断tryAcquireShard(arg)的返回值是否小于0,小于0代表没有获取到锁,大于0表示获取到锁;
    
    2.如果小于0(没有获取到读锁(共享锁)),执行doAcquireShared(arg)方法,将线程封装成node存放到AQS队列中, 等待后续的唤醒(获取到锁不会执行该方法)

     

    先来看看tryAcquireShared(int unsed)

    protected final int tryAcquireShared(int unused) {
        /**
        * 1.如果有其他线程持有写锁,返回-1(即不允许获取读锁(避免读到脏数据));
        *
        * 2.判断获取读锁的线程是否应该被挂起以及尝试获取读锁是否成功;
        *   关于readerShouldBlock()方法,对于公平锁以及非公平锁,有两种不同的实现
        *
        *   公平锁:判断head节点的next节点是否为空或者next节点对应线程是否是当前线程,如果不为空且是当前线程,不应该被阻塞,否则应该被阻塞。(体现公平性原则)
        *          h != t &&((s = h.next) == null || s.thread != Thread.currentThread())
    * *   非公平锁:判断head节点的next节点不为空并且是否是获取写锁的节点,如果是获取写锁的节点,不能与获取写锁的节点争抢,获取读锁失败,应该被阻塞。
    *      (读锁不应该与写锁抢占资源)(h = head) != null &&(s = h.next) != null &&!s.isShared() &&s.thread != null; * * 3.如果获取读锁成功; *   3.1.如果读锁的获取次数(包括重入)为0(即没有线程获取读锁,或者获取读锁的线程刚释放锁),则将firstReader设置为当前线程,firstReaderHoldCount赋值为1. *   3.2.如果读锁获取次数不为0,firstReader为当前线程,firstReaderHoldCount加1(重入) *   3.3.如果读锁获取次数不为0,第一次获取读锁的线程不是当前线程,更新最后一次获取读锁的线程,以及该线程获取读锁的次数(包含重入) * * 4.条件2不满足时,执行fullTryAcquireShared(current)方法
    *
    *
    * 代码截图需要截断,故直接贴的源码。
    */ Thread current = Thread.currentThread(); int c = getState(); if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; int r = sharedCount(c); if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { if (r == 0) { firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { firstReaderHoldCount++; } else { HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; } return 1; } return fullTryAcquireShared(current); }

      关于fullTryAcquireShared(Thread current)方法,会去再次尝试获取读锁

    /**
    * 这段代码是为了处理获取读锁的线程不应该被挂起,但是CAS操作失败的获取锁线程,增加CAS成功的机会
    * * 1.获取锁的状态值
    * * 2.判断写锁是否被占用,如果被占用的线程不是当前线程,return -1;
    * * 3.如果没有线程获取读锁,且写锁的获取线程需要被挂起(该步骤是为了确保可重入锁成功)
    * * 3.1.判断是否第一个获取读锁的线程时候是当前线程,如果是执行后续的CAS操作
    * * 3.2.如果不是,此时rh为null,获取对应的缓存,如果缓存为空,或者对应线程不是当前线程,这个地方会执行初始化的操作
    * * 3.3.如果缓存不为空且对应线程是当前线程,会执行后续CAS操作,否则返回-1
    * * 4.执行后续CAS获取锁操作(与tryAcquireShared一样) */ final int fullTryAcquireShared(Thread current) { HoldCounter rh = null; for (;;) { int c = getState(); if (exclusiveCount(c) != 0) { if (getExclusiveOwnerThread() != current) return -1; // else we hold the exclusive lock; blocking here // would cause deadlock. } else if (readerShouldBlock()) { // Make sure we're not acquiring read lock reentrantly if (firstReader == current) { // assert firstReaderHoldCount > 0; } else { if (rh == null) { rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) { rh = readHolds.get(); if (rh.count == 0) readHolds.remove(); } } if (rh.count == 0) return -1; } } if (sharedCount(c) == MAX_COUNT) throw new Error("Maximum lock count exceeded"); if (compareAndSetState(c, c + SHARED_UNIT)) { if (sharedCount(c) == 0) { firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { firstReaderHoldCount++; } else { if (rh == null) rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; cachedHoldCounter = rh; // cache for release } return 1; } } }

    当获取读锁失败,会去执行doAcquireShared(int arg) 方法,将线程信息封装成队列,保存在AQS队列中(与可重入锁ReentrantLock一样)

     写锁

    写锁的获取,调用的是AQS中的acquire方法,其中tryAcquire(arg)是在ReentrantWriteReadLock中实现的。

     关于tryAcquire方法,主要进行以下步骤:

    protected final boolean tryAcquire(int acquires) {
        /*
    * 1.获取锁的状态值,获取写锁的获取次数;
    *
       * 2.如果c!=0 && w==0或者c!=0 && current != getExclusiveOwnerThread(), 即有线程持有读锁或者有线程持有写锁,但持有写锁的线程不是当前线程,返回false;
    *
    * 3.如果2中不满足,则获取锁成功,设置锁的状态值(其实到这里就表示已经表示重入写锁成功了,不需要进行CAS操作,如果不是写锁的重入,或者说获取写锁失败的话,
    * 或被2中的判断拦截);
    *
       * 4.如果2中条件不满足,会判断写锁获取线程是否应该被挂起或者CAS操作时候会失败,如果2中条件满足则不会执行该步骤,会直接在2中返回true/false;
    *
    * 4.1.判断锁是否应该被挂起操作,需要判断是公平锁还是非公平锁
    *
       * 公平锁:h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
    *
    * 首先判断AQS队列是否为空,其次head节点的next节点是否为空获取next节点对应的线程是否是当前线程,如果队列不为空并且next对应线程不是当前线程
    * (即队列中有线程等待),返回true,需要阻塞等待。
    *
       * 非公平锁 : 直接返回false, 因为是非公平锁,不遵循先到先得策略,可以直接CAS操作去抢占锁。
    * * 4.2.如果需要被挂起,或者CAS操作失败,返回false(表明获取锁失败) */ Thread current = Thread.currentThread(); int c = getState(); int w = exclusiveCount(c); if (c != 0) { // (Note: if c != 0 and w == 0 then shared count != 0) if (w == 0 || current != getExclusiveOwnerThread()) return false; if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded"); // Reentrant acquire setState(c + acquires); return true; } if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false; setExclusiveOwnerThread(current); return true; }

    如果获取写锁失败,则会将当前线程信息封装成一个Node对象放到CAS队列中, 具体可查看ReentrantLock中的说明。

    锁的释放

    读锁

    读锁的释放是调用了AQS中的releaseShared(int arg) 方法

     对于tryReleaseShared(arg)方法,是在ReentrantReadWriteLock的内部类Sync中实现的

    1.获取当前线程,判断第一个获取读锁的线程是否是当前线程,如果是并且第一个获取读锁的线程获取读锁的次数不为1,进行减1操作,如果为1,直接将第一个获取读锁的变量置为空
    
    2.获取当前线程对应的缓存信息,没有就初始化一个,判断对应缓存信息中保存的获取读锁数量,小于等于1就remove掉,否则执行减1操作
    
    3.通过自旋CAS操作,设置锁的状态值,并判断是否是等于0,为0表示读锁与写锁都是访问完毕,会去唤醒后续线程。

    对于释放锁成功,也就是说读锁与写锁都释放完了,会执行doReleaseShared()操作唤醒后续节点

    写锁

    读锁的释放是调用了AQS中的releaseShared(int arg) 方法

      对于release(arg)方法,是在ReentrantReadWriteLock的内部类Sync中实现的,相对来说比较简单

    1.判断获取写锁的线程是否是当前线程,如果不是抛出异常
    
    2.判断写锁的获取次数是否为0,如果是,设置获取写锁的线程为null
    
    3.设置锁的状态值,返回true/false(写锁是否都释放)

    如果没有线程获取锁,会去唤醒后续节点(唤醒操作与ReentrantLock中一直)

  • 相关阅读:
    张旭升20162329 2006-2007-2 《Java程序设计》第一周学习总结
    预备作业03——20162329张旭升
    预备作业02 : 体会做中学-20162329 张旭升
    预备作业01——20162329
    FPGA的软核与硬核
    网页调用vlc并播放网络视频
    vue视频插件VLC
    vue+vue-video-player实现弹窗播放视频
    【面向对象程序设计】作业三
    【面向对象程序设计】作业二
  • 原文地址:https://www.cnblogs.com/lenmom/p/12018306.html
Copyright © 2020-2023  润新知