java并发锁ReentrantReadWriteLock读写锁源码分析

1、ReentrantReadWriterLock 基础
所谓读写锁,是对访问资源共享锁和排斥锁,一般的重入性语义为如果对资源加了写锁,其他线程无法再获得写锁与读锁,但是持有写锁的线程,可以对资源加读锁(锁降级);如果一个线程对资源加了读锁,其他线程可以继续加读锁。
java.util.concurrent.locks中关于多写锁的接口:ReadWriteLock。
public interface ReadWriteLock {

    /**

     * Returns the lock used for reading.

     *

     * @return the lock used for reading.

     */

    Lock readLock();

    /**

     * Returns the lock used for writing.

     *

     * @return the lock used for writing.

     */

    Lock writeLock();

}
提一个问题,是否觉得 ReentrantReadWriteLock 会实现 Lock 接口吗?与 ReentrantLock 有什么关系?
答案是否定的,ReentrantReadWriterLock 通过两个内部类实现 Lock 接口,分别是 ReadLock,WriterLock 类。与 ReentrantLock一样,ReentrantReadWriterLock 同样使用自己的内部类Sync(继承AbstractQueuedSynchronizer)实现CLH算法。为了方便对读写锁获取机制的了解,先介绍一下Sync内部类中几个属性,采用了位运算:
/*

         * Read vs write count extraction constants and functions.

         * Lock state is logically divided into two unsigned shorts:

         * The lower one representing the exclusive (writer) lock hold count,

         * and the upper the shared (reader) hold count.

         */

        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;

        /** Returns the number of shared holds represented in count  */

        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }

        /** Returns the number of exclusive holds represented in count  */

        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
首先ReentrantReadWriterLock使用一个32位的int类型来表示锁被占用的线程数(ReentrantLock中的state),用所以,采取的办法是,高16位用来表示读锁占有的线程数量,用低16位表示写锁被同一个线程申请的次数。

SHARED_SHIFT,表示读锁占用的位数,常量16

SHARED_UNIT,   增加一个读锁,按照上述设计,就相当于增加 SHARED_UNIT;
MAX_COUNT    ,表示申请读锁最大的线程数量,为65535
EXCLUSIVE_MASK  :表示计算写锁的具体值时,该值为 15个1,用 getState & EXCLUSIVE_MASK算出写锁的线程数,大于1表示重入。

static int sharedCount(int c)    { return c >>> SHARED_SHIFT; } 

static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
举例说明,比如,现在当前,申请读锁的线程数为13个,写锁一个,那state怎么表示?
上文说过,用一个32位的int类型的高16位表示读锁线程数,13的二进制为 1101,那state的二进制表示为
00000000 00001101 00000000 00000001,十进制数为 851969, 接下在具体获取锁时,需要根据这个 851968 这个值得出上文中的 13 与 1。要算成13,只需要将state 无符号向左移位16位置,得出00000000 00001101,就出13,根据851969要算成低16位置,只需要用该00000000 00001101 00000000 00000001 & 111111111111111(15位),就可以得出00000001,就是利用了1&1得1,1&0得0这个技巧。
移位元素,如果一个数值向左移(<)一位,在没越界(超过该类型表示的最大值)的情况下,想当于操作数 * 2
如果一个数值向右(>) 移动移位,在没有越界的情况下,想到于操作数 除以2。
然后再关注如下几个与线程本地变量相关的属性:   
 
/**

         * The number of reentrant read locks held by current thread.

         * Initialized only in constructor and readObject.

         * Removed whenever a thread's read hold count drops to 0.

         */

        private transient ThreadLocalHoldCounter readHolds;

        /**

         * The hold count of the last thread to successfully acquire

         * readLock. This saves ThreadLocal lookup in the common case

         * where the next thread to release is the last one to

         * acquire. This is non-volatile since it is just used

         * as a heuristic, and would be great for threads to cache.

         *

         * <p>Can outlive the Thread for which it is caching the read

         * hold count, but avoids garbage retention by not retaining a

         * reference to the Thread.

         *

         * <p>Accessed via a benign data race; relies on the memory

         * model's final field and out-of-thin-air guarantees.

         */

        private transient HoldCounter cachedHoldCounter;

        /**

         * firstReader is the first thread to have acquired the read lock.

         * firstReaderHoldCount is firstReader's hold count.

         *

         * <p>More precisely, firstReader is the unique thread that last

         * changed the shared count from 0 to 1, and has not released the

         * read lock since then; null if there is no such thread.

         *

         * <p>Cannot cause garbage retention unless the thread terminated

         * without relinquishing its read locks, since tryReleaseShared

         * sets it to null.

         *

         * <p>Accessed via a benign data race; relies on the memory

         * model's out-of-thin-air guarantees for references.

         *

         * <p>This allows tracking of read holds for uncontended read

         * locks to be very cheap.

         */

        private transient Thread firstReader = null;

        private transient int firstReaderHoldCount;

上述这4个变量,其实就是完成一件事情,将获取读锁的线程放入线程本地变量(ThreadLocal),方便从整个上 下文,根据当前线程获取持有锁的次数信息。其实 firstReader,firstReaderHoldCount ,cachedHoldCounter 这三个变量就是为readHolds变量服务的,是一个优化手段,尽量减少直接使用readHolds.get方法的次数,firstReader与firstReadHoldCount保存第一个获取读锁的线程,也就是readHolds中并不会保存第一个获取读锁的线程;cachedHoldCounter 缓存的是最后一个获取线程的HolderCount信息,该变量主要是在如果当前线程多次获取读锁时,减少从readHolds中获取HoldCounter的次数。请结合如下代码理解上述观点:

                if (r == 0) {

                    firstReader = current;

                    firstReaderHoldCount = 1;

                } else if (firstReader == current) {

                    firstReaderHoldCount++;

                } else {

                    HoldCounter rh = cachedHoldCounter;

                    if (rh == null || rh.tid != current.getId())

                        cachedHoldCounter = rh = readHolds.get();

                    else if (rh.count == 0)

                        readHolds.set(rh);

                    rh.count++;

                }

2、ReentrantReadWriterLock源码分析

大家可以点击加群【JAVA架构知识学习讨论群】473984645,(如多你想跳槽换工作,但是技术又不够,或者工作遇到了瓶颈,我这里有一个Java的免费直播课程,讲的是高端的知识点,只要有1-5年的开发工作经验可以加群找我要课堂链接。)注意:是免费的  没有开发经验的误入。

2.1 ReadLock 源码分析

2.1.1 lock方法

/**

         * Acquires the read lock.

         *

         * <p>Acquires the read lock if the write lock is not held by

         * another thread and returns immediately.

         *

         * <p>If the write lock is held by another thread then

         * the current thread becomes disabled for thread scheduling

         * purposes and lies dormant until the read lock has been acquired.

         */

        public void lock() {

            sync.acquireShared(1);

        }

sync.acquireShared方法存在于AbstractQueuedSynchronizer类中,

/**

     * Acquires in shared mode, ignoring interrupts.  Implemented by

     * first invoking at least once {@link #tryAcquireShared},

     * returning on success.  Otherwise the thread is queued, possibly

     * repeatedly blocking and unblocking, invoking {@link

     * #tryAcquireShared} until success.

     *

     * @param arg the acquire argument.  This value is conveyed to

     *        {@link #tryAcquireShared} but is otherwise uninterpreted

     *        and can represent anything you like.

     */

    public final void acquireShared(int arg) {

        if (tryAcquireShared(arg) < 0)    //@1

            doAcquireShared(arg);           //@2

    }

根据常识,具体获取锁的过程在子类中实现,果不其然,tryAcquireShared方法在ReentrantReadWriterLock的Sync类中实现

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();    //@1 start

            int c = getState();

            if (exclusiveCount(c) != 0 &&

                getExclusiveOwnerThread() != current)

                return -1;                                                     // @1 end

            int r = sharedCount(c);

            if (!readerShouldBlock() &&                          

                r < MAX_COUNT &&

                compareAndSetState(c, c + SHARED_UNIT)) {    // @2

                if (r == 0) {                                      //@21                               

                    firstReader = current;

                    firstReaderHoldCount = 1;

                } else if (firstReader == current) {  //@22

                    firstReaderHoldCount++;

                } else {                                            // @23

                    HoldCounter rh = cachedHoldCounter;

                    if (rh == null || rh.tid != current.getId())

                        cachedHoldCounter = rh = readHolds.get();

                    else if (rh.count == 0)

                        readHolds.set(rh);

                    rh.count++;

                }

                return 1;

            }

            return fullTryAcquireShared(current);      // @3

        }
尝试获取共享锁代码解读:
@1 start--end ,如果有线程已经抢占了写锁,并且不是当前线程,则直接返回-1,通过排队获取锁。
@2,如果线程不需要阻塞,并且获取读锁的线程数没有超过最大值,并且使用 CAS更新共享锁线程数量成功的话;表示成获取读锁,然后进行内部变量的相关更新操作;先关注一下,成功获取读锁后,内部变量的更新操作:
@21,如果r=0, 表示,当前线程为第一个获取读锁的线程
@22,如果第一个获取读锁的对象为当前对象,将firstReaderHoldCount 加一
@23,成功获取锁后,如果不是第一个获取多锁的线程,将该线程持有锁的次数信息,放入线程本地变量中,方便在整个请求上下文(请求锁、释放锁等过程中)使用持有锁次数信息。
@3 在讲解代码@3之前,我们先重点分析@2处的第一个条件,是否需要阻塞方法:readerShouldBlock,在具体的子类中,现在查看的是NonfairSync中的方法:
final boolean readerShouldBlock() {

            /* As a heuristic to avoid indefinite writer starvation,

             * block if the thread that momentarily appears to be head

             * of queue, if one exists, is a waiting writer.  This is

             * only a probabilistic effect since a new reader will not

             * block if there is a waiting writer behind other enabled

             * readers that have not yet drained from the queue.

             */

            return apparentlyFirstQueuedIsExclusive();   //该方法,具体又是在 AbstractQueuedSynchronizer中

        }

/**

     * Returns {@code true} if the apparent first queued thread, if one

     * exists, is waiting in exclusive mode.  If this method returns

     * {@code true}, and the current thread is attempting to acquire in

     * shared mode (that is, this method is invoked from {@link

     * #tryAcquireShared}) then it is guaranteed that the current thread

     * is not the first queued thread.  Used only as a heuristic in

     * ReentrantReadWriteLock.

     */

    final boolean apparentlyFirstQueuedIsExclusive() {

        Node h, s;

        return (h = head) != null &&

            (s = h.next)  != null &&

            !s.isShared()         &&

            s.thread != null;

    }
该方法如果头节点不为空,并头节点的下一个节点不为空,并且不是共享模式【独占模式,写锁】、并且线程不为空。则返回true,说明有当前申请读锁的线程占有写锁,并有其他写锁在申请。为什么要判断head节点的下一个节点不为空,或是thread不为空呢?因为第一个节点head节点是当前持有写锁的线程,也就是当前申请读锁的线程,这里,也就是锁降级的关键所在,如果占有的写锁不是当前线程,那线程申请读锁会直接失败。
现在继续回到@3,讲解如果第一次尝试获取读锁失败后,该如何处理。首先,进入该方法的条件如下:
没有写锁被占用时,尝试通过一次CAS去获取锁时,更新失败(说明有其他读锁在申请)。
 当前线程占有写锁,并且没有有其他写锁在当前线程的下一个节点等待获取写锁。;其实如果是这种情况,除非当前线程占有锁的下个线程取消,否则进入fullTryAcquireShared方法也无法获取锁。
/**

         * 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) {                                     //@31

                    if (getExclusiveOwnerThread() != current)

                        return -1;

                    // else we hold the exclusive lock; blocking here

                    // would cause deadlock.

                } else if (readerShouldBlock()) {                             //@32

                    // Make sure we're not acquiring read lock reentrantly

                    if (firstReader == current) {                              //@33

                        // assert firstReaderHoldCount > 0;

                    } else {                                                              //@34

                        if (rh == null) {

                            rh = cachedHoldCounter;

                            if (rh == null || rh.tid != current.getId()) {

                                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)) {     // @35

                    if (sharedCount(c) == 0) {

                        firstReader = current;

                        firstReaderHoldCount = 1;

                    } else if (firstReader == current) {

                        firstReaderHoldCount++;

                    } else {

                        if (rh == null)

                            rh = cachedHoldCounter;

                        if (rh == null || rh.tid != current.getId())

                            rh = readHolds.get();

                        else if (rh.count == 0)

                            readHolds.set(rh);

                        rh.count++;

                        cachedHoldCounter = rh; // cache for release

                    }

                    return 1;

                }

            }

        }
代码@31,首先再次判断,如果当前线程不是写锁的持有者,直接返回-1,结束尝试获取读锁,需要排队去申请读锁。
代码@32,如果需要阻塞,说明除了当前线程持有写锁外,还有其他线程已经排队在申请写锁,故,即使申请读锁的线程已经持有写锁(写锁内部再次申请读锁,俗称锁降级)还是会失败,因为有其他线程也在申请写锁,此时,只能结束本次申请读锁的请求,转而去排队,否则,将造成死锁。代码@34,就是从readHolds中移除当前线程的持有数,然后返回-1,结束尝试获取锁步骤(结束tryAcquireShared 方法)然后去排队获取。
代码@33,因为,如果当前线程是第一个获取了写锁,那其他线程无法申请写锁(该部分在分析完,读写锁的队列机制后,才回来做更详细的解答。)
代码@35,表示成功获取读锁,后续就是更新readHolds等内部变量,该部分在上文中已有讲解。如果是通过@35尝试获取锁成功,这就是写锁内部--》再次申请读锁(锁降级)的原理。
至此,完成尝试获取锁步骤 tryAcquireShared 方法,我们再次回到 acquireShared,如果返回-1,那么需要排队申请,具体请看 doAcquireShared(arg);
   public final void acquireShared(int arg) {

        if (tryAcquireShared(arg) < 0)    //@1

            doAcquireShared(arg);           //@2

    }

/**

     * Acquires in shared uninterruptible mode.

     * @param arg the acquire argument

     */

    private void doAcquireShared(int arg) {

        final Node node = addWaiter(Node.SHARED);   //@1

        boolean failed = true;

        try {

            boolean interrupted = false;

            for (;;) { // @2,开始自旋重试

                final Node p = node.predecessor();   // @3

                if (p == head) {                                   // @4

                    int r = tryAcquireShared(arg);         

                    if (r >= 0) {

                        setHeadAndPropagate(node, r);    //@5

                        p.next = null; // help GC

                        if (interrupted)

                            selfInterrupt();

                        failed = false;

                        return;

                    }

                }

                if (shouldParkAfterFailedAcquire(p, node) &&

                    parkAndCheckInterrupt())                                              // @6

                    interrupted = true;

            }

        } finally {

            if (failed)

                cancelAcquire(node);

        }

    }
获取共享锁解读:
代码@1,在队列尾部增加一个节点。锁模式为共享模式。
代码@3,获取该节点的前置节点。
代码@4,如果该节点的前置节点为head(头部),为什么前置节点是head时,可以再次尝试呢?在讲解ReentrantLock时,也讲过,head节点的初始化在第一次产生锁争用时初始化,刚开始初始化的head节点是不代表线程的,故可以尝试获取锁。如果获取失败,则将进入到shouldParkAfterFailedAcquire和parkAndCheckInterrupt方法中,线程阻塞,等待被唤醒。
重点分析一下获取锁后的操作:setHeadAndPropagate
/**

     * Sets head of queue, and checks if successor may be waiting

     * in shared mode, if so propagating if either propagate > 0 or

     * PROPAGATE status was set.

     *

     * @param node the node

     * @param propagate the return value from a tryAcquireShared

     */

    private void setHeadAndPropagate(Node node, int propagate) {

        Node h = head; // Record old head for check below 

        setHead(node);

        /*

         * Try to signal next queued node if:

         *   Propagation was indicated by caller,

         *     or was recorded (as h.waitStatus) by a previous operation

         *     (note: this uses sign-check of waitStatus because

         *      PROPAGATE status may transition to SIGNAL.)

         * and

         *   The next node is waiting in shared mode,

         *     or we don't know, because it appears null

         *

         * The conservatism in both of these checks may cause

         * unnecessary wake-ups, but only when there are multiple

         * racing acquires/releases, so most need signals now or soon

         * anyway.

         */

        if (propagate > 0 || h == null || h.waitStatus < 0) {   // @1

            Node s = node.next;

            if (s == null || s.isShared())    // @2

                doReleaseShared();          //@3

        }

    }

/**

     * Sets head of queue to be node, thus dequeuing. Called only by

     * acquire methods.  Also nulls out unused fields for sake of GC

     * and to suppress unnecessary signals and traversals.

     *

     * @param node the node

     */

    private void setHead(Node node) {

        head = node;

        node.thread = null;

        node.prev = null;

    }

/**

     * Release action for shared mode -- signal successor and ensure

     * propagation. (Note: For exclusive mode, release just amounts

     * to calling unparkSuccessor of head if it needs signal.)

     */

    private void doReleaseShared() {

        /*

         * Ensure that a release propagates, even if there are other

         * in-progress acquires/releases.  This proceeds in the usual

         * way of trying to unparkSuccessor of head if it needs

         * signal. But if it does not, status is set to PROPAGATE to

         * ensure that upon release, propagation continues.

         * Additionally, we must loop in case a new node is added

         * while we are doing this. Also, unlike other uses of

         * unparkSuccessor, we need to know if CAS to reset status

         * fails, if so rechecking.

         */

        for (;;) {

            Node h = head;

            if (h != null && h != tail) {   //@4

                int ws = h.waitStatus;

                if (ws == Node.SIGNAL) {   //@5

                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))

                        continue;            // loop to recheck cases

                    unparkSuccessor(h);

                }

                else if (ws == 0 &&

                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))   //@6

                    continue;                // loop on failed CAS

            }

            if (h == head)                   // loop if head changed      //@7

                break;

        }

    }
释放共享锁的步骤:
代码@1,如果读锁(共享锁)获取成功,或头部节点为空,或头节点取消,或刚获取读锁的线程的下一个节点为空,或在节点的下个节点也在申请读锁,则在CLH队列中传播下去唤醒线程,怎么理解这个传播呢,就是只要获取成功到读锁,那就要传播到下一个节点(如果一下个节点继续是读锁的申请,只要成功获取,就再下一个节点,直到队列尾部或为写锁的申请,停止传播)。具体请看doReleaseShared方法。
代码@4,从队列的头部开始遍历每一个节点。
代码@5,如果节点状态为 Node.SIGNAL,将状态设置为0,设置成功,唤醒线程。为什么会设置不成功,可能改节点被取消;还有一种情况就是有多个线程在运行该代码段,这就是PROPAGATE的含义吧,传播,请看代码@7的理解。
代码@6,如果状态为0,则设置为Node.PROPAGATE,设置为传播,该值然后会在什么时候变化呢?在判断该节点的下一个节点是否需要阻塞时,会判断,如果状态不是Node.SIGNAL或取消状态,为了保险起见,会将前置节点状态设置为Node.SIGNAL,然后再次判断,是否需要阻塞。
代码@7,如果处理过一次 unparkSuccessor 方法后,头节点没有发生变化,就退出该方法,那head在什么时候会改变呢?当然在是抢占锁成功的时候,head节点代表获取锁的节点。一旦获取锁成功,则又会进入setHeadAndPropagate方法,当然又会触发doReleaseShared方法,传播特性应该就是表现在这里吧。再想一下,同一时间,可以有多个多线程占有锁,那在锁释放时,写锁的释放比较简单,就是从头部节点下的第一个非取消节点,唤醒线程即可,为了在释放读锁的上下文环境中获取代表读锁的线程,将信息存入在 readHolds ThreadLocal变量中。
到这里为止,读锁的申请就讲解完毕了,先给出如下流程图:
java并发锁ReentrantReadWriteLock读写锁源码分析

尝试获取读锁过程

从队列中获取读锁的流程如下:

java并发锁ReentrantReadWriteLock读写锁源码分析

2.1.2 ReadLock 的 unlock方法详解

public  void unlock() {

        sync.releaseShared(1);

}

//AbstractQueuedSynchronizer的  realseShared方法

public final boolean releaseShared(int arg) {

        if (tryReleaseShared(arg)) {

            doReleaseShared();

            return true;

        }

        return false;

    }

// ReentrantReadWriterLock.Sync tryReleaseShared

protected final boolean tryReleaseShared(int unused) { 

            Thread current = Thread.currentThread();

            if (firstReader == current) {                               // @1 start               

                // assert firstReaderHoldCount > 0;

                if (firstReaderHoldCount == 1)

                    firstReader = null;

                else

                    firstReaderHoldCount--;

            } else {

                HoldCounter rh = cachedHoldCounter;

                if (rh == null || rh.tid != current.getId())

                    rh = readHolds.get();

                int count = rh.count;

                if (count <= 1) {

                    readHolds.remove();

                    if (count <= 0)

                        throw unmatchedUnlockException();

                }

                --rh.count;                                                            // @1 end

            }

            for (;;) {                                                               // @2

                int c = getState();

                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;

            }

        }

AbstractQueuedSynchronizer的doReleaseShared

/**

     * Release action for shared mode -- signal successor and ensure

     * propagation. (Note: For exclusive mode, release just amounts

     * to calling unparkSuccessor of head if it needs signal.)

     */

    private void doReleaseShared() {

        /*

         * Ensure that a release propagates, even if there are other

         * in-progress acquires/releases.  This proceeds in the usual

         * way of trying to unparkSuccessor of head if it needs

         * signal. But if it does not, status is set to PROPAGATE to

         * ensure that upon release, propagation continues.

         * Additionally, we must loop in case a new node is added

         * while we are doing this. Also, unlike other uses of

         * unparkSuccessor, we need to know if CAS to reset status

         * fails, if so rechecking.

         */

        for (;;) {

            Node h = head;

            if (h != null && h != tail) {

                int ws = h.waitStatus;

                if (ws == Node.SIGNAL) {

                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))

                        continue;            // loop to recheck cases

                    unparkSuccessor(h);

                }

                else if (ws == 0 &&

                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))

                    continue;                // loop on failed CAS

            }

            if (h == head)                   // loop if head changed

                break;

        }

    }
锁的释放,比较简单,代码@1,主要是将当前线程所持有的锁的数量信息得到(从firstReader或cachedHoldCounter,或readHolds中获取 ),然后将数量减少1,如果持有数为1,则直接将该线程变量从readHolds ThreadLocal变量中移除,避免垃圾堆积。
代码@2,就是在无限循环中将共享锁的数量减少一,在释放锁阶段,只有当所有的读锁,写锁被占有,才会去执行doReleaseShared 方法。
 
2.2 WriterLock 源码分析
 
2.2.1 lock方法详解
public void lock() {

            sync.acquire(1);

        }

public final void acquire(int arg) {

        if (!tryAcquire(arg) &&

            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

            selfInterrupt();

}

对上述代码是不是似曾相识,对了,在学习ReentrantLock时候,看到的一样,acquire是在AbstractQueuedSynchronizer中,关键是在 tryAcquire方法,是在不同的子类中实现的。那我们将目光移到ReentrantReadWriterLock.Sync中

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) {                                   // @1

                // (Note: if c != 0 and w == 0 then shared count != 0)

                if (w == 0 || current != getExclusiveOwnerThread())                //@2

                    return false;

                if (w + exclusiveCount(acquires) > MAX_COUNT)              

                    throw new Error("Maximum lock count exceeded");

                // Reentrant acquire

                setState(c + acquires);                                                             //@3

                return true;

            }

            if (writerShouldBlock() ||                                                               

                !compareAndSetState(c, c + acquires))                                   //@4

                return false;

            setExclusiveOwnerThread(current);                                             //@5

            return true;

        }
代码@1,如果锁的state不为0,说明有写锁,或读锁,或两种锁持有。
代码@2,如果写锁为0,再加上c!=0,说明此时有读锁,自然返回false,表示只能排队去获取写锁;如果写锁不为0,如果持有写锁的线程不为当前线程,自然返回false,排队去获取写锁。
代码@3,表示,当前线程持有写锁,现在是重入,所以只需要修改锁的额数量即可。
代码@4,表示,表示通过一次CAS去获取锁的时候失败,说明被别的线程抢去了,也返回false,排队去重试获取锁。
代码@5,成获取写锁后,将当前线程设置为占有写锁的线程。尝试获取锁方法结束。如果该方法返回false,则进入到acquireQueue方法去排队获取写锁,写锁的获取过程,与ReentrantLock获取方法一样,就不过多的解读了。
读写锁的实现原理就分析到这了,走过路过的朋友,欢迎拍砖讨论。
 
我这儿整理了比较全面的JAVA相关的面试资料
java并发锁ReentrantReadWriteLock读写锁源码分析
需要领取面试资料的同学,请加群:473984645
java并发锁ReentrantReadWriteLock读写锁源码分析
上一篇:JVM中java类的加载时机(转载:http://blog.csdn.net/chenleixing/article/details/47099725)


下一篇:linux查看硬盘详细信息