ReentrantReadWriteLock 源码分析 结合场景

网上很多大神写了关于AQS和读写锁的源码分析,看了收益良多。但是发现很少有基于读锁和写锁被持有的场景结合源码进行分析的,
所以尝试记录自己的分析结果,如果有人有暇发现了错误,请不吝赐教。
 
1: 读锁请求和释放
readLock.lock()
多个读请求锁,主要是调用ReentrantReadWriteLock的tryAcquireShared()方法
下面仔细分析下这个方法:
 
tryAcquireShared()方法:
  ①:判断是当前否存在独占锁,如果存在独占锁,那么返回-1,进入doAcquireShared方法()
  ②:判断readshouldBlock方法,这个方法判断是否需要当前读阻塞。在公平锁的前提下是看SYN队列是否存在先驱节点;如果是非公平模式则看队列中
        第一个是否为写。(多个读场景不可能阻塞因为 没有写 读是不会进入阻塞队列)
  ③:如果读锁持有的数量小于MAX_COUNT(2^32-1=65535)则,通过CAS操作来比较。如果比较成功则返回成功;如果失败则进入步骤④FullAcqurireShared方法
  ④: FullAcqurireShared()  其实就是重复1,2,3步骤,知道获取到state或者返回-1.这里需要注意的是这里用例ThreadLocal对象readHolds来存储当前线程锁重入的次数。
  如果上述方法返回-1,基本上归结为公平模式下SYN的队列中有先驱,或者非公平模式下有阻塞的写在队列头中(这种情况一般是被之前读锁阻塞的写),则进入doAcquired方法。
 
doAcquireShared()方法:
  ①:把当前线程包装成共享类型的Node放入SYN队列中
  ②:如果当前节点前驱为头结点(既当前节点是队列中的第一个阻塞节点),则再一次调用tryAcquireShared()方法
     (需要说明的,这边这样做的原因可能是在这个读锁请求过程中,已阻塞在队列中的的读或者写已经被释放。)
     如果tryAcquiredShared()成功了,则尝试释放队列中的存在后继节点且前继waitStatus小于0 则调用doReleaseShared()方法
  ③:如果第二步失败,则用LockSupport.park()挂起当前节点,进入阻塞状态
 
doReleaseShared()方法:
   这里需要注意的是前一个节点的waitStatus代表后一个节点的状态,如果是singnal状态 则代表后一个节点所在线程已经被挂起。
所以doReleaseShared()方法中 unpark的是singanal状态的节点。队列中下一个节点的unpark()是由前一个节点先前阻塞的doAcquireShared()
方法锁唤醒。
 
readLock.unlock()
tryReleaseShared()方法:
  ①:更具ThreadLocalmap中的 readHolds对象,并把数量-1
 ②:CAS把高位读state-1.如果修改后的state为0 则返回true;否则返回false
  ③:步骤②返回true 代表需要进行doRelaeaseShared方法。这里这样做的原因是 当前锁不被        任何读锁或者写锁持有,则doReleaseShared()。因为如果队列中有写请求,写锁需要等刀所有读释放掉才能获得,后于这个写锁来的读锁,肯定被加入到了队列中;如果队列中没有写请求,那么队列中肯定也不会有阻塞(之前和这个读一起阻塞的读线程已经被unpark掉) 因为读是共享的
 
2:写锁请求和释放
writeLock .lock()
tryAcquire()方法:
      tryAcquire()方法  RRWL重写了这个方法 
①: 如果state不为0 且写锁数为0,则代表当前有读锁被持有,当前写阻塞;如果state不为0,写锁数w不为0且独占线程=本身,则当前lock请求为锁的重入
②:如果state为0,则表示当前没有写锁或者读锁被持有。判断writerShouldBlock()当前线程是否要阻塞:如果公平模式下,判断是否有前驱节点,非公平模式
下,直接返回false
③:步骤②返回true 则当前线程获得锁;返回false,则用当前线程封装一个独占节点Node.  EXCLUSIVE,并加入AQS的队列,进入步骤acquireQueued()
 
acquireQueued()方法:
①: 以当前线程构造独占节点并加入到AQS的队列中
②:如果当前的前驱节点是head节点,则再次调用tryAcquired方法,这边再次判断的原因是可能的读锁或者写锁已经释放,如果tryAcquired失败
      则挂起当前线程
 
writeLock .unlock()
①:tryRelease()方法:
    unLock必须是取得锁的线程,因为是独占的 写锁只有一个 所以判断独占线程是否一致 不一致则抛出异常;一致就把state-1,如果写锁被持有数为0
则把独占线程置成null。
②:unparkSuccessor后继节点
    1)判断head是否为空,既判断AQS队列中是否有元素,如果有head节点不为null且waitStatus不为0,则进入步骤2)
    2)进入unparkSuccessor()方法,这个方法意义和名字一样 释放后续的阻塞线程
         2.1)如果当前节点状态小于0则给他置0,这里当前节点状态代表后继节点线程的状态
         2.2)如果后继节点是null或者waitStatus>0(既为CANCELLED取消状态,则跳过),否则unpark()线程
 这种情况,可能是当前写锁被持有,后续来了N个读锁的请求或者写锁请求,这几个锁请求显然被park()变成阻塞状态,放在了队列中。
unLock的unparkSuccessor,唤醒了队列中的head,依次唤醒队列的后续节点,这里需要注意的是,唤醒的顺序 不一定是获得锁的顺序!
 
 
 
 
 

猜你喜欢

转载自givemefive555.iteye.com/blog/2252087