java进阶之路-java同步系列之ReentrantReadWriteLock源码解析

Posted LuckyZhouStar

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java进阶之路-java同步系列之ReentrantReadWriteLock源码解析相关的知识,希望对你有一定的参考价值。

本文参照java同步系列之ReentrantReadWriteLock源码解析

简介

读写锁是一种特殊的锁,它把对共享资源的访问分为读访问和写访问,多个线程可以同时对共享资源进行读访问,但是同一时间只能有一个线程对共享资源进行写访问,使用读写锁可以极大地提高并发量。

读锁源码解读

lock操作

 @ReservedStackAccess
        protected final int tryAcquireShared(int unused) 
            /*
             * Walkthrough:
             * 1. If write lock held by another thread, fail.
             * 2. Otherwise, this thread is eligible for
             *    lock wrt state, so ask if it should block
             *    because of queue policy. If not, try
             *    to grant by CASing state and updating count.
             *    Note that step does not check for reentrant
             *    acquires, which is postponed to full version
             *    to avoid having to check hold count in
             *    the more typical non-reentrant case.
             * 3. If step 2 fails either because thread
             *    apparently not eligible or CAS fails or count
             *    saturated, chain to version with full retry loop.
             */
            Thread current = Thread.currentThread();
            //获取当前的同步状态
            int c = getState();
            //如果当前有排它锁,或者当前的线程不是自己,就返回-1,阻塞当前线程,后面这一步操作是为了判断如果已经有写锁的话,是可以立即获取读锁的
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
                //获取读锁的数量
            int r = sharedCount(c);
            //满足条件的话,尝试更新state状态
            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 != LockSupport.getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                
                return 1;
            
            return fullTryAcquireShared(current);
        

        /**
         * Full version of acquire for reads, that handles CAS misses
         * and reentrant reads not dealt with in tryAcquireShared.
         */
        final int fullTryAcquireShared(Thread current) 
            /*
             * This code is in part redundant with that in
             * tryAcquireShared but is simpler overall by not
             * complicating tryAcquireShared with interactions between
             * retries and lazily reading hold counts.
             */
            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 != LockSupport.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 != LockSupport.getThreadId(current))
                            rh = readHolds.get();
                        else if (rh.count == 0)
                            readHolds.set(rh);
                        rh.count++;
                        cachedHoldCounter = rh; // cache for release
                    
                    return 1;
                
            
        

我们来看看大致的逻辑:

(1)先尝试获取读锁;

(2)如果成功了直接结束;

(3)如果失败了,进入doAcquireShared()方法;

(4)doAcquireShared()方法中首先会生成一个新节点并进入AQS队列中;

(5)如果头节点正好是当前节点的上一个节点,再次尝试获取锁;

(6)如果成功了,则设置头节点为新节点,并传播;

(7)传播即唤醒下一个读节点(如果下一个节点是读节点的话);

(8)如果头节点不是当前节点的上一个节点或者(5)失败,则阻塞当前线程等待被唤醒;

(9)唤醒之后继续走(5)的逻辑;

在整个逻辑中是在哪里连续唤醒读节点的呢?

答案是在doAcquireShared()方法中,在这里一个节点A获取了读锁后,会唤醒下一个读节点B,这时候B也会获取读锁,然后B继续唤醒C,依次往复,也就是说这里的节点是一个唤醒一个这样的形式,而不是一个节点获取了读锁后一次性唤醒后面所有的读节点。采用链表的形式进行唤醒操作。

unlock操作

protected final boolean tryReleaseShared(int unused)
Thread current = Thread.currentThread();
if (firstReader == current)
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount–;
else
HoldCounter rh = cachedHoldCounter;
if (rh == null ||
rh.tid != LockSupport.getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= 1)
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();

–rh.count;

for (;?
int c = getState();
//cas原理来释放当前的锁操作计数器
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
// Releasing the read lock has no effect on readers,
// but it may allow waiting writers to proceed if
// both read and write locks are now free.
return nextc == 0;

解锁的大致流程如下:

(1)将当前线程重入的次数减1;

(2)将共享锁总共被获取的次数减1;

(3)如果共享锁获取的次数减为0了,说明共享锁完全释放了,那就唤醒下一个节点;

ABC三个节点各获取了一次共享锁,三者释放的顺序分别为ACB,那么最后B释放共享锁的时候tryReleaseShared()才会返回true,进而才会唤醒下一个节点D。

写锁操作

lock操作

  protected final boolean tryAcquire(int acquires) 
            /*
             * Walkthrough:
             * 1. If read count nonzero or write count nonzero
             *    and owner is a different thread, fail.
             * 2. If count would saturate, fail. (This can only
             *    happen if count is already nonzero.)
             * 3. Otherwise, this thread is eligible for lock if
             *    it is either a reentrant acquire or
             *    queue policy allows it. If so, update state
             *    and set owner.
             */
            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)
                //如果有共享锁或者当前不是本线程直接返回false
                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;
        

写锁获取的过程大致如下:

(1)尝试获取锁;

(2)如果有读者占有着读锁,尝试获取写锁失败;

(3)如果有其它线程占有着写锁,尝试获取写锁失败;

(4)如果是当前线程占有着写锁,尝试获取写锁成功,state值加1;

(5)如果没有线程占有着锁(state==0),当前线程尝试更新state的值,成功了表示尝试获取锁成功,否则失败;

(6)尝试获取锁失败以后,进入队列排队,等待被唤醒;

(7)后续逻辑跟ReentrantLock是一致;

unlock操作

  protected final boolean tryRelease(int releases) 
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            int nextc = getState() - releases;
            boolean free = exclusiveCount(nextc) == 0;
            if (free)
                setExclusiveOwnerThread(null);
            setState(nextc);
            return free;
        

写锁释放的过程大致为:

(1)先尝试释放锁,即状态变量state的值减1;

(2)如果减为0了,说明完全释放了锁;

(3)完全释放了锁才唤醒下一个等待的节点;

总结

1)ReentrantReadWriteLock采用读写锁的思想,能提高并发的吞吐量;

(2)读锁使用的是共享锁,多个读锁可以一起获取锁,互相不会影响,即读读不互斥;

(3)读写、写读和写写是会互斥的,前者占有着锁,后者需要进入AQS队列中排队;

(4)多个连续的读线程是一个接着一个被唤醒的,而不是一次性唤醒所有读线程;

(5)只有多个读锁都完全释放了才会唤醒下一个写线程;

(6)只有写锁完全释放了才会唤醒下一个等待者,这个等待者有可能是读线程,也可能是写线程;
(7) 内部把state字段进行了拆分,高16位是共享锁的数量,低16位是排他锁的数量

以上是关于java进阶之路-java同步系列之ReentrantReadWriteLock源码解析的主要内容,如果未能解决你的问题,请参考以下文章

打通Java工程师职业进阶之路,“大内高手”出手不凡

JAVA进阶之路-ReentrantLock的公平锁和非公平锁

JAVA进阶之路-java中的位操作

JAVA进阶之路-CountDownLatch源码走读

Kubernetes进阶之路(九)Service系列之ClusterIP&NodePort

JAVA进阶之路-AbstractQueuedSynchronizer(AQS)源码走读