ReentrantReadWriteLock

跟風遠走 提交于 2019-12-11 01:22:10

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中一直)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!