2、如果可以获取读锁,判断是否应该阻塞等待,在公平获取锁方式中,同步队列中有其他线程在等待,则应该去排队按照FIFO顺序获取锁,非公平获取锁方式,可以直接去竞争获取锁。
3、可以获取锁,则尝试cas更新state的值,更新成功,获取到锁。
final int fullTryAcquireShared(Thread current){ HoldCounter rh = null; //无限循环 for (;;) { //获取同步锁状态 int c = getState(); //判断写锁值不为0,且不是当前线程,不可获取读锁 if (exclusiveCount(c) != 0) { if (getExclusiveOwnerThread() != current) return -1; } else if (readerShouldBlock()) { //没有线程获取到写锁情况,公平获取锁情况, //同步队列中有其他线程等待锁,该方法主要是在需要排队等待,计数器重入次数==0情况,清除计数器 if (firstReader == current) { //此处firstReader !=null, 则第1个获取读锁的线程还没释放锁,可允许该线程继续重入获取锁 //计数器count一定>0 } else { if (rh == null) { rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) { rh = readHolds.get(); if (rh.count == 0) //清除计数器 readHolds.remove(); } } // 为什么rh.count == 0就不让线程获取到锁了,基于公平获取锁方式,去同步队列中等待 if (rh.count == 0) return -1; } } //获取读锁线程超过最大限制值 65535 if (sharedCount(c) == MAX_COUNT) throw new Error("Maximum lock count exceeded"); // cas执行读锁值+1 if (compareAndSetState(c, c + SHARED_UNIT)) { if (sharedCount(c) == 0) { //1,第一个获取读锁 firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { //2,第一个获取读锁重入 firstReaderHoldCount++; } else { //3,非第一个线程获取读锁,存在多个线程获取读锁 if (rh == null) rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; //缓存计数器变量记录此次获取读锁线程的计数器 cachedHoldCounter = rh; // cache for release } return 1; } } }tryAcquireShared 返回< 0, 获取锁失败,执行 doAcquireShared
在获取读锁失败后,执行以下步骤:
1、将节点加入同步队列中
2、如果前置节点是头节点,将再次尝试获取锁,如果成功,设置当前节点为head节点,并根据tryAcquireShared方法的返回值r判断是否需要继续唤醒后继节点,如果 r大于0,需要继续唤醒后继节点,r=0不需要唤醒后继节点。
3、如果前置节点不是头节点,则在队列中找到安全位置,设置前置节点 ws=SIGNAL, 挂起等待。
private void doAcquireShared(int arg) { final Node node = addWaiter(Node.SHARED); boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head) { //如果前继节点是头节点,再次尝试获取共享锁 int r = tryAcquireShared(arg); //r>=0,表示获取到锁, //r=0,表示不需要唤醒后继节点 //r>0,需要继续唤醒后继节点 if (r >= 0) { //该方法实现2个步骤 //1,设置当前节点为头节点 //2,r>0情况会继续唤醒后继节点 setHeadAndPropagate(node, r); //旧的头节点移出队列 p.next = null; // help GC if (interrupted) selfInterrupt(); failed = false; return; } } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }setHeadAndPropagate 该方法是与独占锁获取锁的区别之处,获取到锁后,设置为头结点还需要继续传播下去。
private void setHeadAndPropagate(Node node, int propagate) { //记录是的旧的头节点 Node h = head; // Record old head for check //设置当前获取到锁节点为头节点 setHead(node); //propagate >0,表示还需要继续唤醒后继节点 //旧的头节点和新头节点为空,或者ws<0,满足条件之一,尝试去唤醒后继节点 if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) { Node s = node.next; //后继节点为空或者是共享节点(获取读锁的线程) if (s == null || s.isShared()) doReleaseShared(); } }doReleaseShared 方法较难理解,在释放锁中也有调用,留着后面一起分析。
4.2、释放读锁 public void unlock() { sync.releaseShared(1); }AQS中释放共锁方法releaseShared
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }