1、ReentrantReadWriterLock 基础
所谓读写锁,是对访问资源共享锁和排斥锁,一般的重入性语义为如果对资源加了写锁,其他线程无法再获得写锁与读锁,但是持有写锁的线程,可以对资源加读锁(锁降级);如果一个线程对资源加了读锁,其他线程可以继续加读锁。
java.util.concurrent.locks中关于多写锁的接口:ReadWriteLock。
  1. public interface ReadWriteLock {
  2.  
  3. /**
  4.  
  5. * Returns the lock used for reading.
  6.  
  7. *
  8.  
  9. * @return the lock used for reading.
  10.  
  11. */
  12.  
  13. Lock readLock();
  14.  
  15. /**
  16.  
  17. * Returns the lock used for writing.
  18.  
  19. *
  20.  
  21. * @return the lock used for writing.
  22.  
  23. */
  24.  
  25. Lock writeLock();
  26.  
  27. }
提一个问题,是否觉得 ReentrantReadWriteLock 会实现 Lock 接口吗?与 ReentrantLock 有什么关系?
答案是否定的,ReentrantReadWriterLock 通过两个内部类实现 Lock 接口,分别是 ReadLock,WriterLock 类。与 ReentrantLock一样,ReentrantReadWriterLock 同样使用自己的内部类Sync(继承AbstractQueuedSynchronizer)实现CLH算法。为了方便对读写锁获取机制的了解,先介绍一下Sync内部类中几个属性,采用了位运算:
  1. /*
  2.  
  3. * Read vs write count extraction constants and functions.
  4.  
  5. * Lock state is logically divided into two unsigned shorts:
  6.  
  7. * The lower one representing the exclusive (writer) lock hold count,
  8.  
  9. * and the upper the shared (reader) hold count.
  10.  
  11. */
  12.  
  13. static final int SHARED_SHIFT = 16;
  14.  
  15. static final int SHARED_UNIT = (1 << SHARED_SHIFT);
  16.  
  17. static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
  18.  
  19. static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
  20.  
  21. /** Returns the number of shared holds represented in count */
  22.  
  23. static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
  24.  
  25. /** Returns the number of exclusive holds represented in count */
  26.  
  27. 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表示重入。

  1. static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
  2.  
  3. 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。
然后再关注如下几个与线程本地变量相关的属性:   
 
  1. /**
  2.  
  3. * The number of reentrant read locks held by current thread.
  4.  
  5. * Initialized only in constructor and readObject.
  6.  
  7. * Removed whenever a thread's read hold count drops to 0.
  8.  
  9. */
  10.  
  11. private transient ThreadLocalHoldCounter readHolds;
  12.  
  13. /**
  14.  
  15. * The hold count of the last thread to successfully acquire
  16.  
  17. * readLock. This saves ThreadLocal lookup in the common case
  18.  
  19. * where the next thread to release is the last one to
  20.  
  21. * acquire. This is non-volatile since it is just used
  22.  
  23. * as a heuristic, and would be great for threads to cache.
  24.  
  25. *
  26.  
  27. * <p>Can outlive the Thread for which it is caching the read
  28.  
  29. * hold count, but avoids garbage retention by not retaining a
  30.  
  31. * reference to the Thread.
  32.  
  33. *
  34.  
  35. * <p>Accessed via a benign data race; relies on the memory
  36.  
  37. * model's final field and out-of-thin-air guarantees.
  38.  
  39. */
  40.  
  41. private transient HoldCounter cachedHoldCounter;
  42.  
  43. /**
  44.  
  45. * firstReader is the first thread to have acquired the read lock.
  46.  
  47. * firstReaderHoldCount is firstReader's hold count.
  48.  
  49. *
  50.  
  51. * <p>More precisely, firstReader is the unique thread that last
  52.  
  53. * changed the shared count from 0 to 1, and has not released the
  54.  
  55. * read lock since then; null if there is no such thread.
  56.  
  57. *
  58.  
  59. * <p>Cannot cause garbage retention unless the thread terminated
  60.  
  61. * without relinquishing its read locks, since tryReleaseShared
  62.  
  63. * sets it to null.
  64.  
  65. *
  66.  
  67. * <p>Accessed via a benign data race; relies on the memory
  68.  
  69. * model's out-of-thin-air guarantees for references.
  70.  
  71. *
  72.  
  73. * <p>This allows tracking of read holds for uncontended read
  74.  
  75. * locks to be very cheap.
  76.  
  77. */
  78.  
  79. private transient Thread firstReader = null;
  80.  
  81. private transient int firstReaderHoldCount;

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

  1. if (r == 0) {
  2.  
  3. firstReader = current;
  4.  
  5. firstReaderHoldCount = 1;
  6.  
  7. } else if (firstReader == current) {
  8.  
  9. firstReaderHoldCount++;
  10.  
  11. } else {
  12.  
  13. HoldCounter rh = cachedHoldCounter;
  14.  
  15. if (rh == null || rh.tid != current.getId())
  16.  
  17. cachedHoldCounter = rh = readHolds.get();
  18.  
  19. else if (rh.count == 0)
  20.  
  21. readHolds.set(rh);
  22.  
  23. rh.count++;
  24.  
  25. }

2、ReentrantReadWriterLock源码分析

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

2.1 ReadLock 源码分析

2.1.1 lock方法

  1. /**
  2.  
  3. * Acquires the read lock.
  4.  
  5. *
  6.  
  7. * <p>Acquires the read lock if the write lock is not held by
  8.  
  9. * another thread and returns immediately.
  10.  
  11. *
  12.  
  13. * <p>If the write lock is held by another thread then
  14.  
  15. * the current thread becomes disabled for thread scheduling
  16.  
  17. * purposes and lies dormant until the read lock has been acquired.
  18.  
  19. */
  20.  
  21. public void lock() {
  22.  
  23. sync.acquireShared(1);
  24.  
  25. }

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

  1. /**
  2.  
  3. * Acquires in shared mode, ignoring interrupts. Implemented by
  4.  
  5. * first invoking at least once {@link #tryAcquireShared},
  6.  
  7. * returning on success. Otherwise the thread is queued, possibly
  8.  
  9. * repeatedly blocking and unblocking, invoking {@link
  10.  
  11. * #tryAcquireShared} until success.
  12.  
  13. *
  14.  
  15. * @param arg the acquire argument. This value is conveyed to
  16.  
  17. * {@link #tryAcquireShared} but is otherwise uninterpreted
  18.  
  19. * and can represent anything you like.
  20.  
  21. */
  22.  
  23. public final void acquireShared(int arg) {
  24.  
  25. if (tryAcquireShared(arg) < 0) //@1
  26.  
  27. doAcquireShared(arg); //@2
  28.  
  29. }

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

  1. protected final int tryAcquireShared(int unused) {
  2.  
  3. /*
  4.  
  5. * Walkthrough:
  6.  
  7. * 1. If write lock held by another thread, fail.
  8.  
  9. * 2. Otherwise, this thread is eligible for
  10.  
  11. * lock wrt state, so ask if it should block
  12.  
  13. * because of queue policy. If not, try
  14.  
  15. * to grant by CASing state and updating count.
  16.  
  17. * Note that step does not check for reentrant
  18.  
  19. * acquires, which is postponed to full version
  20.  
  21. * to avoid having to check hold count in
  22.  
  23. * the more typical non-reentrant case.
  24.  
  25. * 3. If step 2 fails either because thread
  26.  
  27. * apparently not eligible or CAS fails or count
  28.  
  29. * saturated, chain to version with full retry loop.
  30.  
  31. */
  32.  
  33. Thread current = Thread.currentThread(); //@1 start
  34.  
  35. int c = getState();
  36.  
  37. if (exclusiveCount(c) != 0 &&
  38.  
  39. getExclusiveOwnerThread() != current)
  40.  
  41. return -1; // @1 end
  42.  
  43. int r = sharedCount(c);
  44.  
  45. if (!readerShouldBlock() &&
  46.  
  47. r < MAX_COUNT &&
  48.  
  49. compareAndSetState(c, c + SHARED_UNIT)) { // @2
  50.  
  51. if (r == 0) { //@21
  52.  
  53. firstReader = current;
  54.  
  55. firstReaderHoldCount = 1;
  56.  
  57. } else if (firstReader == current) { //@22
  58.  
  59. firstReaderHoldCount++;
  60.  
  61. } else { // @23
  62.  
  63. HoldCounter rh = cachedHoldCounter;
  64.  
  65. if (rh == null || rh.tid != current.getId())
  66.  
  67. cachedHoldCounter = rh = readHolds.get();
  68.  
  69. else if (rh.count == 0)
  70.  
  71. readHolds.set(rh);
  72.  
  73. rh.count++;
  74.  
  75. }
  76.  
  77. return 1;
  78.  
  79. }
  80.  
  81. return fullTryAcquireShared(current); // @3
  82.  
  83. }
尝试获取共享锁代码解读:
@1 start--end ,如果有线程已经抢占了写锁,并且不是当前线程,则直接返回-1,通过排队获取锁。
@2,如果线程不需要阻塞,并且获取读锁的线程数没有超过最大值,并且使用 CAS更新共享锁线程数量成功的话;表示成获取读锁,然后进行内部变量的相关更新操作;先关注一下,成功获取读锁后,内部变量的更新操作:
@21,如果r=0, 表示,当前线程为第一个获取读锁的线程
@22,如果第一个获取读锁的对象为当前对象,将firstReaderHoldCount 加一
@23,成功获取锁后,如果不是第一个获取多锁的线程,将该线程持有锁的次数信息,放入线程本地变量中,方便在整个请求上下文(请求锁、释放锁等过程中)使用持有锁次数信息。
@3 在讲解代码@3之前,我们先重点分析@2处的第一个条件,是否需要阻塞方法:readerShouldBlock,在具体的子类中,现在查看的是NonfairSync中的方法:
  1. final boolean readerShouldBlock() {
  2.  
  3. /* As a heuristic to avoid indefinite writer starvation,
  4.  
  5. * block if the thread that momentarily appears to be head
  6.  
  7. * of queue, if one exists, is a waiting writer. This is
  8.  
  9. * only a probabilistic effect since a new reader will not
  10.  
  11. * block if there is a waiting writer behind other enabled
  12.  
  13. * readers that have not yet drained from the queue.
  14.  
  15. */
  16.  
  17. return apparentlyFirstQueuedIsExclusive(); //该方法,具体又是在 AbstractQueuedSynchronizer中
  18.  
  19. }
  20.  
  21. /**
  22.  
  23. * Returns {@code true} if the apparent first queued thread, if one
  24.  
  25. * exists, is waiting in exclusive mode. If this method returns
  26.  
  27. * {@code true}, and the current thread is attempting to acquire in
  28.  
  29. * shared mode (that is, this method is invoked from {@link
  30.  
  31. * #tryAcquireShared}) then it is guaranteed that the current thread
  32.  
  33. * is not the first queued thread. Used only as a heuristic in
  34.  
  35. * ReentrantReadWriteLock.
  36.  
  37. */
  38.  
  39. final boolean apparentlyFirstQueuedIsExclusive() {
  40.  
  41. Node h, s;
  42.  
  43. return (h = head) != null &&
  44.  
  45. (s = h.next) != null &&
  46.  
  47. !s.isShared() &&
  48.  
  49. s.thread != null;
  50.  
  51. }
该方法如果头节点不为空,并头节点的下一个节点不为空,并且不是共享模式【独占模式,写锁】、并且线程不为空。则返回true,说明有当前申请读锁的线程占有写锁,并有其他写锁在申请。为什么要判断head节点的下一个节点不为空,或是thread不为空呢?因为第一个节点head节点是当前持有写锁的线程,也就是当前申请读锁的线程,这里,也就是锁降级的关键所在,如果占有的写锁不是当前线程,那线程申请读锁会直接失败。
现在继续回到@3,讲解如果第一次尝试获取读锁失败后,该如何处理。首先,进入该方法的条件如下:
没有写锁被占用时,尝试通过一次CAS去获取锁时,更新失败(说明有其他读锁在申请)。
 当前线程占有写锁,并且没有有其他写锁在当前线程的下一个节点等待获取写锁。;其实如果是这种情况,除非当前线程占有锁的下个线程取消,否则进入fullTryAcquireShared方法也无法获取锁。
  1. /**
  2.  
  3. * Full version of acquire for reads, that handles CAS misses
  4.  
  5. * and reentrant reads not dealt with in tryAcquireShared.
  6.  
  7. */
  8.  
  9. final int fullTryAcquireShared(Thread current) {
  10.  
  11. /*
  12.  
  13. * This code is in part redundant with that in
  14.  
  15. * tryAcquireShared but is simpler overall by not
  16.  
  17. * complicating tryAcquireShared with interactions between
  18.  
  19. * retries and lazily reading hold counts.
  20.  
  21. */
  22.  
  23. HoldCounter rh = null;
  24.  
  25. for (;;) {
  26.  
  27. int c = getState();
  28.  
  29. if (exclusiveCount(c) != 0) { //@31
  30.  
  31. if (getExclusiveOwnerThread() != current)
  32.  
  33. return -1;
  34.  
  35. // else we hold the exclusive lock; blocking here
  36.  
  37. // would cause deadlock.
  38.  
  39. } else if (readerShouldBlock()) { //@32
  40.  
  41. // Make sure we're not acquiring read lock reentrantly
  42.  
  43. if (firstReader == current) { //@33
  44.  
  45. // assert firstReaderHoldCount > 0;
  46.  
  47. } else { //@34
  48.  
  49. if (rh == null) {
  50.  
  51. rh = cachedHoldCounter;
  52.  
  53. if (rh == null || rh.tid != current.getId()) {
  54.  
  55. rh = readHolds.get();
  56.  
  57. if (rh.count == 0)
  58.  
  59. readHolds.remove();
  60.  
  61. }
  62.  
  63. }
  64.  
  65. if (rh.count == 0)
  66.  
  67. return -1;
  68.  
  69. }
  70.  
  71. }
  72.  
  73. if (sharedCount(c) == MAX_COUNT)
  74.  
  75. throw new Error("Maximum lock count exceeded");
  76.  
  77. if (compareAndSetState(c, c + SHARED_UNIT)) { // @35
  78.  
  79. if (sharedCount(c) == 0) {
  80.  
  81. firstReader = current;
  82.  
  83. firstReaderHoldCount = 1;
  84.  
  85. } else if (firstReader == current) {
  86.  
  87. firstReaderHoldCount++;
  88.  
  89. } else {
  90.  
  91. if (rh == null)
  92.  
  93. rh = cachedHoldCounter;
  94.  
  95. if (rh == null || rh.tid != current.getId())
  96.  
  97. rh = readHolds.get();
  98.  
  99. else if (rh.count == 0)
  100.  
  101. readHolds.set(rh);
  102.  
  103. rh.count++;
  104.  
  105. cachedHoldCounter = rh; // cache for release
  106.  
  107. }
  108.  
  109. return 1;
  110.  
  111. }
  112.  
  113. }
  114.  
  115. }
代码@31,首先再次判断,如果当前线程不是写锁的持有者,直接返回-1,结束尝试获取读锁,需要排队去申请读锁。
代码@32,如果需要阻塞,说明除了当前线程持有写锁外,还有其他线程已经排队在申请写锁,故,即使申请读锁的线程已经持有写锁(写锁内部再次申请读锁,俗称锁降级)还是会失败,因为有其他线程也在申请写锁,此时,只能结束本次申请读锁的请求,转而去排队,否则,将造成死锁。代码@34,就是从readHolds中移除当前线程的持有数,然后返回-1,结束尝试获取锁步骤(结束tryAcquireShared 方法)然后去排队获取。
代码@33,因为,如果当前线程是第一个获取了写锁,那其他线程无法申请写锁(该部分在分析完,读写锁的队列机制后,才回来做更详细的解答。)
代码@35,表示成功获取读锁,后续就是更新readHolds等内部变量,该部分在上文中已有讲解。如果是通过@35尝试获取锁成功,这就是写锁内部--》再次申请读锁(锁降级)的原理。
至此,完成尝试获取锁步骤 tryAcquireShared 方法,我们再次回到 acquireShared,如果返回-1,那么需要排队申请,具体请看 doAcquireShared(arg);
  1. public final void acquireShared(int arg) {
  2.  
  3. if (tryAcquireShared(arg) < 0) //@1
  4.  
  5. doAcquireShared(arg); //@2
  6.  
  7. }
  8.  
  9. /**
  10.  
  11. * Acquires in shared uninterruptible mode.
  12.  
  13. * @param arg the acquire argument
  14.  
  15. */
  16.  
  17. private void doAcquireShared(int arg) {
  18.  
  19. final Node node = addWaiter(Node.SHARED); //@1
  20.  
  21. boolean failed = true;
  22.  
  23. try {
  24.  
  25. boolean interrupted = false;
  26.  
  27. for (;;) { // @2,开始自旋重试
  28.  
  29. final Node p = node.predecessor(); // @3
  30.  
  31. if (p == head) { // @4
  32.  
  33. int r = tryAcquireShared(arg);
  34.  
  35. if (r >= 0) {
  36.  
  37. setHeadAndPropagate(node, r); //@5
  38.  
  39. p.next = null; // help GC
  40.  
  41. if (interrupted)
  42.  
  43. selfInterrupt();
  44.  
  45. failed = false;
  46.  
  47. return;
  48.  
  49. }
  50.  
  51. }
  52.  
  53. if (shouldParkAfterFailedAcquire(p, node) &&
  54.  
  55. parkAndCheckInterrupt()) // @6
  56.  
  57. interrupted = true;
  58.  
  59. }
  60.  
  61. } finally {
  62.  
  63. if (failed)
  64.  
  65. cancelAcquire(node);
  66.  
  67. }
  68.  
  69. }
获取共享锁解读:
代码@1,在队列尾部增加一个节点。锁模式为共享模式。
代码@3,获取该节点的前置节点。
代码@4,如果该节点的前置节点为head(头部),为什么前置节点是head时,可以再次尝试呢?在讲解ReentrantLock时,也讲过,head节点的初始化在第一次产生锁争用时初始化,刚开始初始化的head节点是不代表线程的,故可以尝试获取锁。如果获取失败,则将进入到shouldParkAfterFailedAcquire和parkAndCheckInterrupt方法中,线程阻塞,等待被唤醒。
重点分析一下获取锁后的操作:setHeadAndPropagate
  1. /**
  2.  
  3. * Sets head of queue, and checks if successor may be waiting
  4.  
  5. * in shared mode, if so propagating if either propagate > 0 or
  6.  
  7. * PROPAGATE status was set.
  8.  
  9. *
  10.  
  11. * @param node the node
  12.  
  13. * @param propagate the return value from a tryAcquireShared
  14.  
  15. */
  16.  
  17. private void setHeadAndPropagate(Node node, int propagate) {
  18.  
  19. Node h = head; // Record old head for check below
  20.  
  21. setHead(node);
  22.  
  23. /*
  24.  
  25. * Try to signal next queued node if:
  26.  
  27. * Propagation was indicated by caller,
  28.  
  29. * or was recorded (as h.waitStatus) by a previous operation
  30.  
  31. * (note: this uses sign-check of waitStatus because
  32.  
  33. * PROPAGATE status may transition to SIGNAL.)
  34.  
  35. * and
  36.  
  37. * The next node is waiting in shared mode,
  38.  
  39. * or we don't know, because it appears null
  40.  
  41. *
  42.  
  43. * The conservatism in both of these checks may cause
  44.  
  45. * unnecessary wake-ups, but only when there are multiple
  46.  
  47. * racing acquires/releases, so most need signals now or soon
  48.  
  49. * anyway.
  50.  
  51. */
  52.  
  53. if (propagate > 0 || h == null || h.waitStatus < 0) { // @1
  54.  
  55. Node s = node.next;
  56.  
  57. if (s == null || s.isShared()) // @2
  58.  
  59. doReleaseShared(); //@3
  60.  
  61. }
  62.  
  63. }
  64.  
  65. /**
  66.  
  67. * Sets head of queue to be node, thus dequeuing. Called only by
  68.  
  69. * acquire methods. Also nulls out unused fields for sake of GC
  70.  
  71. * and to suppress unnecessary signals and traversals.
  72.  
  73. *
  74.  
  75. * @param node the node
  76.  
  77. */
  78.  
  79. private void setHead(Node node) {
  80.  
  81. head = node;
  82.  
  83. node.thread = null;
  84.  
  85. node.prev = null;
  86.  
  87. }
  88.  
  89. /**
  90.  
  91. * Release action for shared mode -- signal successor and ensure
  92.  
  93. * propagation. (Note: For exclusive mode, release just amounts
  94.  
  95. * to calling unparkSuccessor of head if it needs signal.)
  96.  
  97. */
  98.  
  99. private void doReleaseShared() {
  100.  
  101. /*
  102.  
  103. * Ensure that a release propagates, even if there are other
  104.  
  105. * in-progress acquires/releases. This proceeds in the usual
  106.  
  107. * way of trying to unparkSuccessor of head if it needs
  108.  
  109. * signal. But if it does not, status is set to PROPAGATE to
  110.  
  111. * ensure that upon release, propagation continues.
  112.  
  113. * Additionally, we must loop in case a new node is added
  114.  
  115. * while we are doing this. Also, unlike other uses of
  116.  
  117. * unparkSuccessor, we need to know if CAS to reset status
  118.  
  119. * fails, if so rechecking.
  120.  
  121. */
  122.  
  123. for (;;) {
  124.  
  125. Node h = head;
  126.  
  127. if (h != null && h != tail) { //@4
  128.  
  129. int ws = h.waitStatus;
  130.  
  131. if (ws == Node.SIGNAL) { //@5
  132.  
  133. if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
  134.  
  135. continue; // loop to recheck cases
  136.  
  137. unparkSuccessor(h);
  138.  
  139. }
  140.  
  141. else if (ws == 0 &&
  142.  
  143. !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) //@6
  144.  
  145. continue; // loop on failed CAS
  146.  
  147. }
  148.  
  149. if (h == head) // loop if head changed //@7
  150.  
  151. break;
  152.  
  153. }
  154.  
  155. }
释放共享锁的步骤:
代码@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方法详解

  1. public void unlock() {
  2.  
  3. sync.releaseShared(1);
  4.  
  5. }
  6.  
  7. //AbstractQueuedSynchronizer的 realseShared方法
  8.  
  9. public final boolean releaseShared(int arg) {
  10.  
  11. if (tryReleaseShared(arg)) {
  12.  
  13. doReleaseShared();
  14.  
  15. return true;
  16.  
  17. }
  18.  
  19. return false;
  20.  
  21. }
  22.  
  23. // ReentrantReadWriterLock.Sync tryReleaseShared
  24.  
  25. protected final boolean tryReleaseShared(int unused) {
  26.  
  27. Thread current = Thread.currentThread();
  28.  
  29. if (firstReader == current) { // @1 start
  30.  
  31. // assert firstReaderHoldCount > 0;
  32.  
  33. if (firstReaderHoldCount == 1)
  34.  
  35. firstReader = null;
  36.  
  37. else
  38.  
  39. firstReaderHoldCount--;
  40.  
  41. } else {
  42.  
  43. HoldCounter rh = cachedHoldCounter;
  44.  
  45. if (rh == null || rh.tid != current.getId())
  46.  
  47. rh = readHolds.get();
  48.  
  49. int count = rh.count;
  50.  
  51. if (count <= 1) {
  52.  
  53. readHolds.remove();
  54.  
  55. if (count <= 0)
  56.  
  57. throw unmatchedUnlockException();
  58.  
  59. }
  60.  
  61. --rh.count; // @1 end
  62.  
  63. }
  64.  
  65. for (;;) { // @2
  66.  
  67. int c = getState();
  68.  
  69. int nextc = c - SHARED_UNIT;
  70.  
  71. if (compareAndSetState(c, nextc))
  72.  
  73. // Releasing the read lock has no effect on readers,
  74.  
  75. // but it may allow waiting writers to proceed if
  76.  
  77. // both read and write locks are now free.
  78.  
  79. return nextc == 0;
  80.  
  81. }
  82.  
  83. }
  84.  
  85. AbstractQueuedSynchronizerdoReleaseShared
  86.  
  87. /**
  88.  
  89. * Release action for shared mode -- signal successor and ensure
  90.  
  91. * propagation. (Note: For exclusive mode, release just amounts
  92.  
  93. * to calling unparkSuccessor of head if it needs signal.)
  94.  
  95. */
  96.  
  97. private void doReleaseShared() {
  98.  
  99. /*
  100.  
  101. * Ensure that a release propagates, even if there are other
  102.  
  103. * in-progress acquires/releases. This proceeds in the usual
  104.  
  105. * way of trying to unparkSuccessor of head if it needs
  106.  
  107. * signal. But if it does not, status is set to PROPAGATE to
  108.  
  109. * ensure that upon release, propagation continues.
  110.  
  111. * Additionally, we must loop in case a new node is added
  112.  
  113. * while we are doing this. Also, unlike other uses of
  114.  
  115. * unparkSuccessor, we need to know if CAS to reset status
  116.  
  117. * fails, if so rechecking.
  118.  
  119. */
  120.  
  121. for (;;) {
  122.  
  123. Node h = head;
  124.  
  125. if (h != null && h != tail) {
  126.  
  127. int ws = h.waitStatus;
  128.  
  129. if (ws == Node.SIGNAL) {
  130.  
  131. if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
  132.  
  133. continue; // loop to recheck cases
  134.  
  135. unparkSuccessor(h);
  136.  
  137. }
  138.  
  139. else if (ws == 0 &&
  140.  
  141. !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
  142.  
  143. continue; // loop on failed CAS
  144.  
  145. }
  146.  
  147. if (h == head) // loop if head changed
  148.  
  149. break;
  150.  
  151. }
  152.  
  153. }
锁的释放,比较简单,代码@1,主要是将当前线程所持有的锁的数量信息得到(从firstReader或cachedHoldCounter,或readHolds中获取 ),然后将数量减少1,如果持有数为1,则直接将该线程变量从readHolds ThreadLocal变量中移除,避免垃圾堆积。
代码@2,就是在无限循环中将共享锁的数量减少一,在释放锁阶段,只有当所有的读锁,写锁被占有,才会去执行doReleaseShared 方法。
 
2.2 WriterLock 源码分析
 
2.2.1 lock方法详解
  1. public void lock() {
  2.  
  3. sync.acquire(1);
  4.  
  5. }
  6.  
  7. public final void acquire(int arg) {
  8.  
  9. if (!tryAcquire(arg) &&
  10.  
  11. acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  12.  
  13. selfInterrupt();
  14.  
  15. }
  16.  
  17. 对上述代码是不是似曾相识,对了,在学习ReentrantLock时候,看到的一样,acquire是在AbstractQueuedSynchronizer中,关键是在 tryAcquire方法,是在不同的子类中实现的。那我们将目光移到ReentrantReadWriterLock.Sync
  18.  
  19. protected final boolean tryAcquire(int acquires) {
  20.  
  21. /*
  22.  
  23. * Walkthrough:
  24.  
  25. * 1. If read count nonzero or write count nonzero
  26.  
  27. * and owner is a different thread, fail.
  28.  
  29. * 2. If count would saturate, fail. (This can only
  30.  
  31. * happen if count is already nonzero.)
  32.  
  33. * 3. Otherwise, this thread is eligible for lock if
  34.  
  35. * it is either a reentrant acquire or
  36.  
  37. * queue policy allows it. If so, update state
  38.  
  39. * and set owner.
  40.  
  41. */
  42.  
  43. Thread current = Thread.currentThread();
  44.  
  45. int c = getState();
  46.  
  47. int w = exclusiveCount(c);
  48.  
  49. if (c != 0) { // @1
  50.  
  51. // (Note: if c != 0 and w == 0 then shared count != 0)
  52.  
  53. if (w == 0 || current != getExclusiveOwnerThread()) //@2
  54.  
  55. return false;
  56.  
  57. if (w + exclusiveCount(acquires) > MAX_COUNT)
  58.  
  59. throw new Error("Maximum lock count exceeded");
  60.  
  61. // Reentrant acquire
  62.  
  63. setState(c + acquires); //@3
  64.  
  65. return true;
  66.  
  67. }
  68.  
  69. if (writerShouldBlock() ||
  70.  
  71. !compareAndSetState(c, c + acquires)) //@4
  72.  
  73. return false;
  74.  
  75. setExclusiveOwnerThread(current); //@5
  76.  
  77. return true;
  78.  
  79. }
代码@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. pandas相关操作

    import pandas as pd import numpy as np ''' 一.创建df 1.定义df :传递字典 1.1每一列的名称作为键 每个键都有一个数组作为值[key:数组] 1.2 ...

  2. 【设计模式】FactoryPattern工厂模式

    Factory Pattern 简单工厂模式 将变化的部分封装起来 //简单工厂 class SimpleProductFactory{ Product createProduct(String ty ...

  3. Windows Server 2008 R2 官方简体中文免费企业版/标准版/数据中心版

    Windows Server 2008 R2是一款微软发布的Windows服务器操作系统,和之前发布的Windows Server 2008相比功能更为完善运行更为稳定,提升了系统管理弹性.虚拟化.网 ...

  4. Redis项目实战,一些经验总结

    来源:https://my.oschina.net/u/920698/blog/3031587 背景 Redis 是一个开源的内存数据结构存储系统. 可以作为数据库.缓存和消息中间件使用. 支持多种类 ...

  5. python 批量爬取四级成绩单

    使用本文爬取成绩大致有几个步骤:1.提取表格(或其他格式文件——含有姓名,身份证等信息)中的数据,为进行准考证爬取做准备.2.下载准考证文件并提取出准考证和姓名信息.3.根据得到信息进行数据分析和存储 ...

  6. java反射-学习

    使用Java反射机制可以在运行时期获取Java类的信息,可获取以下相关的内容: Class对象 类名 修饰符 包信息 父类 实现的接口 构造器 方法 变量 注解 简单的反射例子: 1.获取class对 ...

  7. NoWarningNoError(第八组)----Krad项目报告

    Alpha阶段展示及总结 Github地址:https://github.com/NiceKingWei/krad 项目地址:119.29.32.204/krad.html 一.项目概况 本组的项目为 ...

  8. Docker基础(上)

    Docker基础(上) 链接:https://pan.baidu.com/s/1KQjKml2OZAReYwOvpWD9XQ 提取码:6vo8 复制这段内容后打开百度网盘手机App,操作更方便哦 1. ...

  9. Oracle之分页问题

    前面的Top-N问题使用了reownum,但是又遇到个分页问题,将表emp的4行为1页输出,前4行很好做: select rownum,empno,ename,sal from emp ; 但是4-- ...

  10. 巨好看的xshell配色

    推荐字体Lucida console [FlatUI] text=e5e5e5 cyan(bold)=16a085 text(bold)=ecf0f1 magenta=9b59b6 green=2ec ...