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变量中。
到这里为止,读锁的申请就讲解完毕了,先给出如下流程图:

尝试获取读锁过程

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

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相关的面试资料
需要领取面试资料的同学,请加群:473984645

java并发锁ReentrantReadWriteLock读写锁源码分析的更多相关文章

  1. 多线程高并发编程(4) -- ReentrantReadWriteLock读写锁源码分析

    背景: ReentrantReadWriteLock把锁进行了细化,分为了写锁和读锁,即独占锁和共享锁.独占锁即当前所有线程只有一个可以成功获取到锁对资源进行修改操作,共享锁是可以一起对资源信息进行查 ...

  2. java读写锁源码分析(ReentrantReadWriteLock)

    读锁的调用,最终委派给其内部类 Sync extends AbstractQueuedSynchronizer /** * 获取读锁,如果写锁不是由其他线程持有,则获取并立即返回: * 如果写锁被其他 ...

  3. Java并发编程笔记之ReentrantLock源码分析

    ReentrantLock是可重入的独占锁,同时只能有一个线程可以获取该锁,其他获取该锁的线程会被阻塞后放入该锁的AQS阻塞队列里面. 首先我们先看一下ReentrantLock的类图结构,如下图所示 ...

  4. Java并发编程笔记之AbstractQueuedSynchronizer源码分析

    为什么要说AbstractQueuedSynchronizer呢? 因为AbstractQueuedSynchronizer是JUC并发包中锁的底层支持,AbstractQueuedSynchroni ...

  5. Java并发编程笔记之CopyOnWriteArrayList源码分析

    并发包中并发List只有CopyOnWriteArrayList这一个,CopyOnWriteArrayList是一个线程安全的ArrayList,对其进行修改操作和元素迭代操作都是在底层创建一个拷贝 ...

  6. Java并发编程笔记之ThreadLocal源码分析

    多线程的线程安全问题是微妙而且出乎意料的,因为在没有进行适当同步的情况下多线程中各个操作的顺序是不可预期的,多线程访问同一个共享变量特别容易出现并发问题,特别是多个线程需要对一个共享变量进行写入时候, ...

  7. Java并发编程中线程池源码分析及使用

    当Java处理高并发的时候,线程数量特别的多的时候,而且每个线程都是执行很短的时间就结束了,频繁创建线程和销毁线程需要占用很多系统的资源和时间,会降低系统的工作效率. 参考http://www.cnb ...

  8. Java并发编程笔记之CyclicBarrier源码分析

    JUC 中 回环屏障 CyclicBarrier 的使用与分析,它也可以实现像 CountDownLatch 一样让一组线程全部到达一个状态后再全部同时执行,但是 CyclicBarrier 可以被复 ...

  9. Java并发编程笔记之PriorityBlockingQueue源码分析

    JDK 中无界优先级队列PriorityBlockingQueue 内部使用堆算法保证每次出队都是优先级最高的元素,元素入队时候是如何建堆的,元素出队后如何调整堆的平衡的? PriorityBlock ...

随机推荐

  1. VS2013编译程序出现error C4996: 'std::_Fill_n': Function call with parameters that may be unsafe

    最近按照BiliBil网站Visual C++网络项目实战视频教程,使用VS2013编写一个基于MFC的对话框程序HttpSourceViewer,采用了WinHttp库.Boost xpressiv ...

  2. mysql 查看数据库最大连接数

    show variables like '%max_connections%'; navicat 切换到命令行: navicat查看建表语句: 选中表,右键,对象信息,选择DDL

  3. react学习笔记_03-组件&props

    组件 & Props的学习 组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思. 组件,从概念上类似于 JavaScript 函数.它接受任意的入参(即 “props”) ...

  4. Administrator 被禁用

    Administrator 被禁用 问题: 重装系统后出现输入用户名和密码的情况,原因是Administrator被禁用. 解决方法: 1.开机后shift+重启 –>选择疑难解答 --> ...

  5. The user specified as a definer ('root'@'%') does not exist解决

    转自:https://www.cnblogs.com/hanxue53/p/5850263.html 解决方法: 2.进入mysql的安装路径之前,要确保你的mysql服务是开启的: 在DOS命令窗口 ...

  6. HumanNet v2:用于疾病研究的人类基因网络 X科研网

    HumanNet v2:用于疾病研究的人类基因网络人类基因网络已被证明在疾病研究的许多方面都很有用,已经开发了许多基于网络的策略来产生关于基因 - 疾病 - 药物关联的假设.预测和组织与特定疾病最相关 ...

  7. Django学习笔记(三)视图

    构建网页内容 视图函数的return具有多种响应类型: 上述函数主要来自django.http,该模块是实现响应功能的核心. 实际开发中可用此模块实现文件下载功能,在index的urls.py和vie ...

  8. jstat性能分析

    垃圾回收统计 S0C:第一个幸存区的大小 S1C:第二个幸存区的大小 S0U:第一个幸存区的使用大小 S1U:第二个幸存区的使用大小 EC:伊甸园区的大小 EU:伊甸园区的使用大小 OC:老年代大小 ...

  9. django 如何重用app

    若有一个已经运行稳定的程序,那么可以将其打包,供其他项目安装使用. 假设django项目的目录结构如下: mysite/ manage.py mysite/ __init__.py settings. ...

  10. 如果全球的沙子都对你发起DDoS攻击,如何破?

    IPv6已来 2016年6月1日开始,苹果规定所有提交至AppStore的应用必须兼容IPv6-only标准.可以预计,2018年底会有大量互联网资源.上网用户使用IPv6协议.这意味着,如果一个互联 ...