ReentrantReadWriteLock 读写锁

  ReentrantLock 中还有 ReentrantReadWriteLock 这么一个变种,读读不互斥,读写互斥,写写互斥。

写锁

  一开始,就是 tryAcquire 获取锁,如果获取失败就返回 false ,那就走 addWaiter 将请求入队,然后 acquireQueued 挂起线程。和 ReentrantLock 一毛一样,那我们就来分析分析它的这个 加锁逻辑有何不同吧。

第一次线程过来
1. 首先获取到当前线程,state (默认肯定是为0的),state 的低16位 w(state 低16位标识写锁,高16位是标识读锁的 )
2. c != 0 那就说明被人加过锁了,我们现在是第一次进来,那么肯定不走这里,先跳过。
3. writerShouldBlock() 没任何逻辑,直接返回false了
4. 然后就是 cas 加锁,成功了就返回false,标记线程锁占用标识。

第 N 次线程过来
1. 首先获取到当前线程,state (假设被人加锁了,那么就是1),w(state 的低16位,写锁标识 )
2. c != 0 那就说明被人加过锁了,进来这个分支。
3. w == 0 ,说明之前加的是读锁,或者 不是当前线程,那就加锁失败返回false
4. 能走到下面的 if ,就说明 之前是当前线程家的写锁,那么 state+1就完了。
5. 最后走到 setExclusiveOwnerThread 设置线程占据锁标识

所以说 写锁 和 读/写锁 都是互斥的压根进不来。所谓的读写锁就是靠 state 的高低16位来搞的。

释放锁就没啥说的了,state-1,占用锁线程置空,然后唤醒头部节点。

读锁

首先上来就是 tryAcquireShared() 尝试获取锁,他有三种情况

(1) 被人加过写锁
1. 获取到当前线程 和 加锁状态 c
2. exclusiveCount(c) 判断低16位是否 != 0 ,如果不等于0,就说明加过写锁了
3. 如果 != 0 ,那么后面再看 当前线程是否加锁线程,不是当前线程就直接返回加锁失败

(2) 没被人加过锁,或者加过读锁,或者自己加过写锁
1. 获取 state 的高16位 c (读锁标识)
2. readerShouldBlock() 判断是否需要阻塞,默认非公平锁肯定不用的,(里面判断依据是 !s.isShared() 如果线程不是加读锁,我们现在加的就是读锁,那么肯定是false)
3. 开始 cas 加锁
3.1 如果 r == 0,说明没被人加过锁、自己是第一个。
3.2 如果 firstReader == current ,说明自己加过锁,现在自己重入加锁
3.3 如果以上都不是,说明别人加过读锁,那么现在在 HoldCounter 记录最后一个加锁线程信息(也就是自己)

(3) r < MAX_COUNT 不成立的时候,没有加锁
1. 此时它会执行 fullTryAcquireShared() 再次尝试获取锁,整体代码其实和上面逻辑没啥区别,就不读了。

  假如上面没有获取到锁,return -1 的话,那就执行阻塞等待的逻辑了,如果节点 p 是头节点,那就说明当前线程是队列中的第一个节点,他就尝试获取锁,如果获取成功就 p.next=null 将自己从队列中摘除掉;反之,打断当前线程,把自己挂起来。

 

 。

posted @ 2021-07-20 14:20  吴磊的  阅读(98)  评论(0编辑  收藏  举报
//生成目录索引列表