文章目录
AbstractQueuedSynchronizer类
aqs是Java提供的一个同步框架,他拥有一个队列和一个状态来控制同步资源的状态。
了解aqs后,通过重写他留下的模板方法可以快速创建一个独占锁或者共享锁。
队列通过两个属性来保存,队列中存储的Node节点则代表等待获取同步资源的线程。
// 队列的头节点
private transient volatile Node head;
// 队列的尾节点
private transient volatile Node tail;
队列中节点声明如下
此处省略部分代码,仅仅为了先了解节点的重要组成
可以看出队列由双向链表构成,每个节点存储当前线程和节点状态。
static final class Node {
// 指示节点在共享模式下等待的标记
static final Node SHARED = new Node();
// 指示节点在独占模式下等待的标记
static final Node EXCLUSIVE = null;
// 如果是在争夺aqs的state状态语义中仅做标识使用,如果是Condition下用该字段链接单向队列
Node nextWaiter;
volatile Node prev;
volatile Node next;
volatile Thread thread;
// 节点的不同状态
// 1 CANCEL 表示当前节点获取锁超时或发生异常需要取消
// 0 表示当前节点的初始状态
// -1 SIGNAL 表示后续节点需要唤醒
// -2 CONDITION 表示当前节点是条件队列中节点
// -3 PROPAGATE 表示当前节点共享锁的一种共享传播状态
volatile int waitStatus;
}
状态通过一个属性来保存
private volatile int state;
aqs的独占锁获取和释放流程
开头介绍了aqs内部构成,此处我们通过独占锁的获取流程了解其内部各方法的作用。
- 获取独占锁流程
-
调用acquire(int arg)方法进行获取。
- arg参数没有强制要求需要什么,用户如果想要自己实现aqs的子类可以自己对于arg进行一套设计,对于ReentrantLock来说,arg是获取锁个数,因为该类可重入。
- acquire源码如下
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
-
tryAcquire方法表明尝试获取锁,返回true表示获取锁成功,反之则表明失败,需要调用addWaiter方法创建一个独占锁等待节点并将当前线程加入队列尾部,调用acquireQueued(Node node)阻塞当前线程并等待锁释放时唤醒重新竞争锁。
-
tryAcquire方法需要子类自己实现,aqs并未给出实现方式,此时我们先不考虑该方法实现细节,只需了解他返回值代表的含义,后续通过分析ReentrantLock将整个流程补全。
-
addWaiter(Node mode)方法
- mode属性表明加入节点为什么类型节点,如果是Node.SHARED表明该节点是一个共享锁节点,此时传入Node.EXCLUSIVE表明该节点为一个独占锁节点,此时传入mode为Node节点的nextWatiter属性。
- 返回值为新创建的节点对象
- 源码如下
private Node addWaiter(Node mode) { Node node = new Node(mode); for (;;) { Node oldTail = tail; if (oldTail != null) { node.setPrevRelaxed(oldTail); if (compareAndSetTail(oldTail, node)) { oldTail.next = node; return node; } } else { initializeSyncQueue(); } } }
- 通过自旋加CAS来保证并发时的正确性,该方法和enq方法的区别是传入值不同,enq传入值为插入队尾的node,返回值也不同,enq方法返回值为旧的尾部node。
- 只有当CAS设置尾部节点成功时,才会将旧尾部next节点链接到新尾部节点,防止并发问题,这也是为何aqs其他遍历队列时从队尾遍历,在入队时保证同一时刻只有一个线程能更改尾部对象
- initializeSyncQueue方法就是通过CAS将head从null设置为一个空的Node节点,如果成功则将尾部指向这个head。
-
acquireQueued(final Node node,int arg)方法是aqs排队的重中之重
- node节点则是已经入队的节点对象,arg则是想要申请共享资源的数量
- 返回值是在阻塞时中断的标志,如果为true则在acquire方法中if为true则需要再次中断,否则什么也不做。如果在自旋中或阻塞中获取锁时线程发生中断,须等到获取锁后才会响应此次中断,并重新进行中断。
- 源码如下
final boolean acquireQueued(final Node node, int arg) { boolean interrupted = false; try { for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC return interrupted; } if (shouldParkAfterFailedAcquire(p, node)) interrupted |= parkAndCheckInterrupt(); } } catch (Throwable t) { cancelAcquire(node); if (interrupted) selfInterrupt(); throw t; } }
- 正常流程下,线程入队列后在此处还有一次获取锁的机会,如果当前队列的前驱节点为队列头部,尝试获取锁,如果获取成功将当前节点设置为新的头节点并返回中断状态,如果失败则进入shouldParkAfterFailedAcquire方法检查是否需要阻塞,当返回true时则表明需要进行阻塞,阻塞方法为parkAndCheckInterrupt方法,该方法使用LockSupport.park(this)阻塞当前线程,本质上是使用UnSafe.park阻塞。
- 可以看到当在自旋过程中发生异常时,将调用cancelAcquire方法取消当前线程节点,并检查是否发生过中断并抛出此次异常。
- setHead(Node node)方法方法很简单直接贴源码。
private void setHead(Node node) { head = node; node.thread = null; node.prev = null; }
-
shouldParkAfterFailedAcquire(Node pred, Node node)方法
- pred节点为当前线程节点的前驱节点,node为当前线程节点。
- 返回值true代表当前线程可以park,当锁释放后,会有其他节点unPark当前线程,返回值为false时需要在acquireQueued方法中在进行一次自旋。
- 源码如下
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) return true; if (ws > 0) { do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { pred.compareAndSetWaitStatus(ws, Node.SIGNAL); } return false; }
- 可以看到只有当前驱节点的信号为SIGNAL时返回true,对于节点的不同状态可以参考上文中Node节点waitStatus属性的注释来对应,对于aqs中节点值小于0有意义,0代表节点的一种初始状态。
- 该方法一共做3件事,第一件事,判断前驱节点waitStatus值为-1返回true让线程阻塞。第二件事,跳过队列中以取消的节点并返回false让当前节点重新进入acquireQueued自旋中,第三件事将前驱节点CAS设置为SIGNAL并返回false。
- 总结该方法就是结合acquireQueued自旋来根据前驱节点的不同状态来进行不同操作。
-
自此通过前边5步了解到aqs中独占锁的加载流程,对于cancelAcquire(Node node)方法介绍如下。
- node节点为需要取消的节点。
- 该方法无返回值,该方法调用时机,第一种情况当前线程获取锁失败入队后自旋中未获取锁之前发生异常会调用。第二种获取锁超时会调用。
- 该方法源码如下
private void cancelAcquire(Node node) { if (node == null) return; node.thread = null; Node pred = node.prev; while (pred.waitStatus > 0) node.prev = pred = pred.prev; Node predNext = pred.next; // 这里不适用cas的原因是因为waitStatus属性由volatile修饰,所以这个写入后对于其他线程访问这个节点的这个属性是立即可见的,而且这个操作也是原子的,所以无须跟其他线程竞争。 node.waitStatus = Node.CANCELLED; if (node == tail && compareAndSetTail(node, pred)) { pred.compareAndSetNext(predNext, null); } else { int ws; if (pred != head && ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && pred.compareAndSetWaitStatus(ws, Node.SIGNAL))) && pred.thread != null) { Node next = node.next; if (next != null && next.waitStatus <= 0) pred.compareAndSetNext(predNext, next); } else { unparkSuccessor(node); } node.next = node; // help GC } }
- 该方法先将当前线程置为null,并跳过当前线程前驱已取消节点,并将当前节点置为CANCELLED供其他线程取消自己,因为如果后续主动将自己从队列摘除失败后因为当前节点值为CANCELLED所以也可以取消。
- 自己主动取消逻辑如下:1.如果当前节点是队尾的话,CAS将队尾设置为当前节点的前驱节点,并将前驱节点的next节点CAS为null,如果失败表示有其他线程正在链接此前驱节点无须操作。2.如果当前节点为头节点则直接unparkSuccessor传入当前节点。3.如果前驱节点不为头节点则判断waitStatus是否为SIGNAL,如果是则直接CAS将前驱节点的后续节点改变,如果CAS失败也无需操心。4. 如果前驱节点的waitStatus不为SIGNAL,要么就为0初始状态,要么就为PROPAGATE状态,对于0来说可能是当前线程节点刚入队还未阻塞就发生异常进行取消,对于这种情况我们可以将它设置成-1,无论后续是否有节点,因为在这里判断时前驱节点一定不为head节点,所以需要将信号设为-1防止当前节点后续还存在节点后信号无法传递,如果为PROPAGATE状态则可直接设置为-1,因为该状态为一个共享锁释放时的一个中间状态,为了防止共享锁释放时无法唤醒后续节点。
- 总结:对于前驱节点的状态设置为-1是没有任何坏处的,因为即使他后边无节点,当唤醒时也做了判空处理,如果有节点信号没传递的话则会出现问题,aqs中有很多操作都是互相依赖的,共享锁中也有很多唤醒操作是多余的,但是应该都是为了保证程序的健壮性。
- 释放独占锁流程
-
相对于获取锁的流程释放锁流程就简单很多,相对于独占锁调用acquire方法加锁,同样释放锁调用release(int arg)方法释放。
- arg参数对于acquire参数拥有相同含义,是由我们来定义的,后续我们通过分析ReentrantLock对于arg参数的定义可以了解该参数含义。
- 返回值为tryRelease方法的返回值,当返回true时通常表示锁完全释放,可供其他线程竞争,反之亦然。
- 源码如下
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
- 当tryRelease方法释放成功时,判断head是否为空,如果不为空则调用unparkSuccessor(h)唤醒节点。
- 由此可看出aqs维护的队列是一个先进先出队列,入队从队尾入,出队时从队首拿。
- head为null表示队列未初始化,因为在获取成功后会将自身节点设置为新的头节点,并清空节点thread属性,也就是头节点始终不为null,而且头节点仅作为一个哨兵使用,不代表任意一个线程。
-
unparkSuccessor(Node node)方法
- 当前方法在释放节点和取消节点时使用
- 当前方法表示唤醒传入节点node的后继节点 3. 该方法无返回值,源码如下
private void unparkSuccessor(Node node) { int ws = node.waitStatus; if (ws < 0) node.compareAndSetWaitStatus(ws, 0); Node s = node.next; if (s == null || s.waitStatus > 0) { s = null; for (Node p = tail; p != node && p != null; p = p.prev) if (p.waitStatus <= 0) s = p; } if (s != null) LockSupport.unpark(s.thread); }
- 将当前节点的传递信号清除,并获得后继节点,如果不为null则调用unPark直接唤醒,如果未空则从队列尾部找到一个离当前节点最近的可用节点,可用节点的定义是不是取消节点。
-
当节点unPark后会从acquireQueued方法中parkAndCheckInterrupt返回,并检查在park后线程是否中断过,然后自旋判断当前节点的前驱节点是否为头节点,如果是头节点则尝试获取锁,否则将调用shouldParkAfterFailedAcquire。
-
自此释放锁流程走完。
aqs的共享锁的获取和释放
- 获取共享锁流程
-
调用acquireShared(int arg)方法获取
- 该方法的含义是获取共享锁。
- 该方法无返回值,传入参数和acquire一致,参数含义需自己定义。
- 该方法源码如下
public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); }
-
tryAcquireShared(int arg)方法
- 该方法的含义是获取共享锁资源,并返回剩余共享锁的数量。
- 该方法返回值为int,返回值含义为剩余共享锁的数量,arg参数和acquireShared方法保持一致
- 该方法是模板方法需自己手动实现,后续通过Semaphore类分析,此刻只需要该方法是获取共享锁方法,返回剩余共享锁数量,如果小于0表示获取共享锁失败。
-
doAcquireShared(int arg)方法
- 该方法是当获取共享锁资源失败时调用,方法含义是将当前线程入等待队列,然后当成功获取共享锁后返回。
- 该方法无返回值,arg参数和acquireShared方法保持一致
- 该方法源码如下
private void doAcquireShared(int arg) { final Node node = addWaiter(Node.SHARED); boolean interrupted = false; try { for (;;) { final Node p = node.predecessor(); if (p == head) { int r = tryAcquireShared(arg); if (r >= 0) { setHeadAndPropagate(node, r); p.next = null; // help GC return; } } if (shouldParkAfterFailedAcquire(p, node)) interrupted |= parkAndCheckInterrupt(); } } catch (Throwable t) { cancelAcquire(node); throw t; } finally { if (interrupted) selfInterrupt(); } }
- 该方法调用addWiter生成共享节点,并入队列尾部,然后进入自旋,判断当前前驱节点是否是头节点,如果是则尝试获取,否则调用shouldParkAfterFailedAcquire方法将前驱节点信号值设为-1,成功设置后返回,调用parkAndCheckInterrupt进行阻塞。
- 总体上来看获取共享锁流程和独占锁流程无区别,区别是tryAcquire返回布尔值,true代表获取锁成功,而tryAcquireShared返回int值,大于等于0表示获取锁成功,因为返回值表示剩余锁个数,还有在设置头节点的时候有区别,我们下文说。
-
setHeadAndPropagate(Node node,int propagate)方法
- node参数表示获取锁的节点,propagate参数表示剩余共享锁数量。
- 无返回值,该方法的主要作用是将已获取共享锁的节点设置为新的头节点及将共享锁的共享传播下去。
- 源码如下
private void setHeadAndPropagate(Node node, int propagate) { Node h = head; setHead(node); // 传播锁 if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) { Node s = node.next; if (s == null || s.isShared()) doReleaseShared(); } }
- setHead(Node node)方法在独占锁时介绍了,就是将当前以获取锁的节点设置为新的头节点,并清空前驱节点和线程的引用。
- 方法的第二部就是传播锁,传播锁我将他分为3钟情况
- propagate > 0 表示当还有可获取的锁时,将锁传播下去。
- h == null || h.waitStatus < 0表示旧的头节点waitStatus小于0时传播锁,此时旧头节点应只有两个值-1或者-3,
如果是-3说明在调用该方法之前,同时有两个线程释放共享锁,其中一个竞争失败,一个竞争成功唤醒当前线程节点,竞争失败将头节点设置为-3。 - 第二次获取新的头节点判断状态是否小于0我认为是一种增强,这次修改是Doug Lea的一次单独的修改,然后提交信息为Recheck need for signal in setHeadAndPropagate。我并未找到一种能走到这里的场景。
- 该传播锁的判断方式会引起不必要的唤醒,不过这些线程唤醒后获取不到锁又会阻塞,我认为没关系,这段代码应该就是为了共享锁更快的传播下去,即使会导致一些不必要的唤醒。
- 补充:waitStatus为-3是Node.PROPAGATE,-1是Node.SIGNAL,在上文介绍NODE节点时介绍过。
- 如果此处代码看不懂可先熟悉释放共享锁的流程再回来查看。
-
获取共享锁的流程到此结束,总体流程和独占锁一致。
- 释放共享锁流程
- 调用releaseShared(int arg)方法释放
- arg参数和acquireShared中含义保持一致。
- 该方法返回值为布尔型,true表示释放锁成功,反之亦然。
- 源代码如下
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }
- 调用tryReleaseShared方法释放共享锁,如果释放成功则调用doReleaseShared方法唤醒队列中节点返回true,否则返回false。
- tryReleaseShared(int arg)方法
- 该方法为释放共享锁的核心方法,不过也是一个模板方法等待自己定义,后续通过Semaphore类分析,此刻只需了解返回true代表释放共享锁成功,反之亦然。
- doReleaseShared()方法
- 该方法为唤醒队列中第一个等待节点,使其从doAcquireShared方法中park位置唤醒,重新参与自旋。
- 该方法无返回值,源代码如下
private void doReleaseShared() { for (;;) { Node h = head; if (h != null && h != tail) { int ws = h.waitStatus; if (ws == Node.SIGNAL) { if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0)) continue; // loop to recheck cases unparkSuccessor(h); } else if (ws == 0 && !h.compareAndSetWaitStatus(0, Node.PROPAGATE)) continue; // loop on failed CAS } if (h == head) // loop if head changed break; } }
- 通过自旋保证该方法总会改变头节点状态,即使发生竞争。
- 如果队列中有节点并且需要信号则通过cas将信号设置为0,并调用unparkSuccessor方法唤醒。
- 如果两个线程同时释放锁,有一个会从cas竞争中失败,此时若头节点未改变则会将waitStatus设置为-3表示信号需要传播,如果头节点改变那通过规则4重新判断。
- 如果h和新头节点不一致表示在此次循环中头节点改变了,此时应重新自旋判断新的头节点,因为当前方法是释放锁成功后释放,必须保证通知成功才会退出。
- 至此共享锁释放过程结束。
ConditionObject类
类介绍
支持await和signal操作,内部使用单链表链住条件队列,当signal唤醒时从队列头部唤醒。
每次调用await和signal方法时,线程需持有AQS的独占锁。
运行流程
- await方法流程
public final void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); Node node = addConditionWaiter(); int savedState = fullyRelease(node); int interruptMode = 0; while (!isOnSyncQueue(node)) { LockSupport.park(this); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; if (node.nextWaiter != null) // clean up if cancelled unlinkCancelledWaiters(); if (interruptMode != 0) reportInterruptAfterWait(interruptMode); }
-
判断线程是否发生中断,如果发生中断则抛出中断异常
-
调用addConditionWaiter方法创建一个条件队列节点
- addConditionWaiter方法返回值为新创建的条件队列节点。
- 源码如下
private Node addConditionWaiter() { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); Node t = lastWaiter; if (t != null && t.waitStatus != Node.CONDITION) { unlinkCancelledWaiters(); t = lastWaiter; } Node node = new Node(Node.CONDITION); if (t == null) firstWaiter = node; else t.nextWaiter = node; lastWaiter = node; return node; }
- 判断是否持有独占锁,如果未持有则抛出异常
- 判断条件队列尾部是否不为空且已取消,如果是取消节点则调用unlinkCancelledWaiters方法从条件队列中移除已取消节点。
- 创建新等待节点插入队尾,如果队尾为空,则将firstWaiter指向新节点,然后将队尾指向当前节点并返回当前节点
由于ConditionObject类大部分方法要求持有独占锁才可操作,所以不会发生并发问题。
-
调用fullyRelease方法释放独占锁
-
调用isOnSyncQueue方法
- isOnSyncQueue方法解析,返回值为true表示节点存于AQS的阻塞队列,如果返回false则表示节点存于条件队列中
- 源码如下
final boolean isOnSyncQueue(Node node) { if (node.waitStatus == Node.CONDITION || node.prev == null) return false; if (node.next != null) return true; return findNodeFromTail(node); } private boolean findNodeFromTail(Node node) { for (Node p = tail;;) { if (p == node) return true; if (p == null) return false; p = p.prev; } }
- 条件队列和阻塞队列共用同一节点Node类,阻塞队列中使用prev和next属性链接,条件队列中使用nextWaiter属性链接。
- 当前节点waitStatus值为CONDITION时表示在条件队列中,prev为null时也表示在条件队列中,每次从条件队列中转移到阻塞队列时都是尾插,所以当prev不为null时表明一定不在条件队列了,但是不一定在阻塞队列中,因为enq方法入队时先将当前节点的前驱节点设置为旧的阻塞队列尾节点,并CAS将当前节点设置为尾节点,如果CAS竞争失败,则前驱节点不为null但是可能并未入阻塞队列。
- 当节点的后继节点不为null时表示在阻塞队列中,如果后继节点不为null则表示在enq入队时CAS竞争失败,则从阻塞队列尾部遍历检查节点是否在阻塞队列中,如果在则返回true,不在则返回false。
- 条件队列的节点被唤醒后会转移到阻塞队列中。所以会由此方法判断。
-
自旋判断当前节点的状态,如果在条件队列中则调用park阻塞,如果在阻塞队列中则结束循环,由于是循环所以可以允许不必要的unPark。
-
每次从park恢复后调用checkInterruptWhileWaiting会检查线程的中断状态。
-
checkInterruptWhileWaiting方法如下
private int checkInterruptWhileWaiting(Node node) { return Thread.interrupted() ? (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : 0; } final boolean transferAfterCancelledWait(Node node) { if (node.compareAndSetWaitStatus(Node.CONDITION, 0)) { enq(node); return true; } // 上述CAS失败,表明我们输给了一个signal方法的CAS竞争,那么在它完成 enq() 之前我们不能继续。 while (!isOnSyncQueue(node)) Thread.yield(); // 让出CPU时间片 return false; }
- 当发生线程中断时调用transferAfterCancelledWait方法将节点取消并转移到阻塞队列中。
- 当由该方法转入的阻塞队列时返回true,当由其他方法转入的阻塞队列时返回false。
- 如果返回true当获取AQS锁后只需抛出异常,如果返回false在获取锁后需要重新进行中断
- 该方法返回中断状态,如果线程未中断则中断状态为0,如果transferAfterCancelledWait方法为true中断状态为THROW_IE,否则为REINTERRUPT。
-
此时从自旋中完成,无论是线程被中断还是被signal唤醒都将继续执行。
-
调用acquireQueued方法获取锁,AQS获取独占锁逻辑,此处不在赘述,当该方法返回true时表示在获取独占锁时发生了中断并且判断中断状态不为THROW_IE,然后将中断状态置为REINTERRUPT。
-
如果条件队列后续有节点,调用unlinkCancelledWaiters方法移除已取消节点。
-
如果中断状态不为0则调用reportInterruptAfterWait方法响应。
private void reportInterruptAfterWait(int interruptMode) throws InterruptedException { if (interruptMode == THROW_IE) throw new InterruptedException(); else if (interruptMode == REINTERRUPT) selfInterrupt(); // Thread.currentThread().interrupt(); }
当是THROW_IE抛出异常,否则重新中断线程。
-
大体流程如下:
- 先判断中断状态,如果发生中断直接响应,否则将等待流程走完在响应,即使中途发生中断。
- 创建条件队列节点,并入队尾
- 释放独占锁
- 判断节点是否存在于条件队列,如果存在则park阻塞,当park阻塞回来后检查中断状态,如果是因为中断则主动将节点转移至阻塞队列,成功后中断状态为THROW_IE,转移过程失败则中断状态为REINTERRUPT。
- 步骤4自旋直到节点转移到阻塞队列,两种可能:线程发生中断主动转移。线程被signal方法唤醒被动转移。
- 自旋结束后获取独占锁
- 清理条件队列中已取消节点
- 检查中断状态并响应中断
- signal方法
public final void signal() { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); Node first = firstWaiter; if (first != null) doSignal(first); } private void doSignal(Node first) { do { if ( (firstWaiter = first.nextWaiter) == null) lastWaiter = null; first.nextWaiter = null; } while (!transferForSignal(first) && (first = firstWaiter) != null); } final boolean transferForSignal(Node node) { /* * 如果节点值不为CONDITION,表示节点已经由其他方法入阻塞队列,此时直接返回false即可 */ if (!node.compareAndSetWaitStatus(Node.CONDITION, 0)) return false; Node p = enq(node); int ws = p.waitStatus; if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL)) LockSupport.unpark(node.thread); return true; }
调用signal方法表示唤醒一个条件队列中的节点,从队首开始唤醒,所以ConditionObject也是维护了一个先进先出的条件队列
- 先判断是否持有独占锁,如果未持有则直接抛出异常
- 判断条件队列队首是否为null,如果为null则结束,如果不为null则调用doSignal(first)方法
- 自旋调用transferForSignal唤醒节点,如果transferForSignal方法返回false表示唤醒失败则继续循环唤醒条件队列下一个节点
- transferForSignal方法中CAS将当前节点waitStatus值改为0,如果失败表示唤醒失败返回false
- 如果成功则将该节点转移到阻塞队列中,并将判断前驱节点状态,如果前驱节点已取消则直接unPark线程,或者将前驱节点状态CAS设置为SIGNAL失败时直接unPark,否则等待AQS独占锁的唤醒。
- 注意此刻的unPark只是从await方法的park中唤醒,并不是AQS阻塞队列的park唤醒。
总结
AQS不仅仅提供了共享锁和独占锁的解决方案,也提供了ConditionObjcet类解决多个线程互相通信的解决方案,相比Object的wait和notify方法,该类维护了一个先进先出队列,比notify随机唤醒一个wait线程会具有可控性。
ReentrantLock类
类介绍
ReentrantLock是一个基于AQS支持可重入锁的一款独占锁,内部实现了公平锁和非公平锁。
内部子类Sync
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
@ReservedStackAccess
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 锁未被持有,当前线程可竞争
if (c == 0) {
// 使用CAS竞争,如果修改成功则表示成功获取锁
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 锁当前已被线程持有,判断是否是当前线程再次获取锁
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 锁已被非当前线程持有,尝试获取锁失败
return false;
}
@ReservedStackAccess
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
final boolean isLocked() {
return getState() != 0;
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
该类是AQS的子类,是公平锁和非公平锁的父类,我们介绍下内部方法的作用,后边根据流程在分析源码。
- nonfairTryAcquire(int acquires) 非公平锁获取独占锁的方法。
- tryRelease(int release) AQS释放锁提供的模板方法实现,非公平锁和公平锁释放锁的方法。
- isHeldExclusively() 当前线程是否持有独占锁,true表示持有。
- newCondition() 获取一个条件,此处先不展开讲解,在CyclicBarrier源码讲解时在详细说明。
- getOwner() 返回当前获取独占锁的线程,如果没有则返回null。
- getHoldCount() 返回当前线程重入锁的次数。
- isLocked() 独占锁是否被获取。
- readObject() 反序列化ReentrantLock。
公平锁和非公平锁的区别
- 公平锁源码如下
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
@ReservedStackAccess
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 锁未被持有,当前线程可竞争
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 锁已被获取,判断是否可重入
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 获取锁失败
return false;
}
}
- 非公平锁源码如下
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
由我们上述aqs源码分析的代码可知,tryAcquire方法是aqs中提供的独占锁获取的模板方法,可以看到非公平锁直接调用父类的nonfairTryAcquire方法获取锁,公平锁是自己实现的获取锁方法。
通过比对得知ReentrantLcok的公平锁和非公平锁在获取时只有当state为0时不同,公平锁会调用hasQueuedPredecessors()方法而非公平锁则直接尝试使用CAS获取锁。由此总结得知ReentrantLock的公平锁和非公平所只有在获取时有区别,公平锁获取时判断aqs等待队列中是否有线程在等待,如果有则返回false,在aqs中tryAcquire返回false则将当前线程节点入队。而非公平锁在获取时总会CAS插队抢占锁,失败时返回false。
hasQueuedPredecessors方法源码如下
public final boolean hasQueuedPredecessors() {
Node h, s;
if ((h = head) != null) {
if ((s = h.next) == null || s.waitStatus > 0) {
for (Node p = tail; p != h && p != null; p = p.prev) {
if (p.waitStatus <= 0)
s = p;
}
}
if (s != null && s.thread != Thread.currentThread())
return true;
}
return false;
}
该类是aqs实现的方法,如果队列不为空且后继节点也不为空且后继节点不是取消节点则返回true,否则从队列尾部遍历到头部寻找到离头部最近的有效节点,如果找到的有效节点不为null代表队列不为空 返回true,否则返回false。找到的节点需不是当前线程,否则也同样返回false。
独占锁获取流程
在非公平锁下独占锁的获取流程。
- 调用ReentrantLock中lock方法
- 该方法只有一行调用sync.acquire(1)
- 由于sync为aqs子类,由我们对aqs源码已知,该方法是获取独占锁的方法
- 然后在aqs的acquire方法中调用子类实现的tryAcquire方法
- 然后其他流程则跟aqs中获取独占锁流程一致
总结:ReentrantLock的设计思路,实现AQS提供的模板方法tryAcquire方法,调用AQS的acquire获取独占锁,当调用模板方法时会默认调用该类重写的tryAcquire方法。
独占锁释放流程
- 调用ReentrantLock中unLock方法
- 该方法只有一行调用sync.release(1)
- 由于sync为aqs子类,由我们对aqs源码已知,该方法是释放独占锁的方法
- 然后在aqs的release方法中调用子类实现的tryRelease方法
- 然后其他流程则跟aqs中释放独占锁流程一致
总结:释放锁流程时不区分公平锁和非公平锁,因为这两个子类均未重写父类Sync的tryRelease方法。
总结
ReentrantLock对于arg参数的定义,数值为加锁数量,如果为n(n>0)表示独占锁被同一线程重入n次,如果为0则表示锁未被任何线程持有。
在我们理解AQS的整体流程后,对于ReentrantLock类的理解非常简单,就只是实现了加锁的流程和对于state的定义,对于加锁后state的值和释放后state的变化。
ReentrantReadWriteLock类
类介绍
该类基于AQS实现了一种读写锁的解决方案,核心思想是将int值的state状态分为两部分,低16位用于写锁的储存,高16位用于读锁的储存,所以该读写锁中读锁或写锁最大获取数量为2^16 - 1个,数量计算为同一线程重入n次记作n次。
内部子类Sync
每个基于AQS的锁实现都会内部实现一个子类去继承自AQS,如果使用独占锁重写tryAcquire和tryRelease方法,参考ReentrantLock只实现了这两个方法就提供了可重入的独占锁,如果使用共享锁重写tryAcquireShared和tryReleaseShared方法。读写锁内部Sync源码如下。
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 6317671515068378041L;
// 共享锁移动位数为16
static final int SHARED_SHIFT = 16;
// 共享锁单位 用于加1使用 该int值表明共享锁中的1
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;
// 传入state,返回值为共享锁数量,通过右移16位将低16位抹去
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
// 传入state返回拥有独占锁个数,通过按位与独占锁掩码获取
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
// 使用该内部类,记录每个共享锁线程重入的数量。通过readHolds维护,cachedHoldCounter缓存。
static final class HoldCounter {
int count; // initially 0
// Use id, not reference, to avoid garbage retention
final long tid = LockSupport.getThreadId(Thread.currentThread());
}
// 将上述子类用ThreadLocal实现
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
// 使用ThreadLocal记录每个线程独自的HoldCount类计数,当线程释放锁时count为0则remove掉。
private transient ThreadLocalHoldCounter readHolds;
// 缓存自上一个从readHolds中获取到的HoldCount计数,用作缓存使用。
private transient HoldCounter cachedHoldCounter;
// 第一个获取共享锁的线程和计数器,当所有人释放共享锁后或者共享锁初始化时获取的第一个线程被记录
private transient Thread firstReader;
private transient int firstReaderHoldCount;
Sync() {
readHolds = new ThreadLocalHoldCounter();
//下边这个setState,由于是volatile类型的,写前后会加StoreStore和StoreLoad屏障保证readHolds写入对所有线程可见
setState(getState()); // ensures visibility of readHolds
}
// 实现公平锁和非公平锁的抽象方法,对于ReentrantLock来说只需要保证获取锁时有插队即可区分公平锁和非公平锁,
// 而读写锁这里需要使用两个方法分别用于读锁和写锁的公平性。
abstract boolean readerShouldBlock();
abstract boolean writerShouldBlock();
@ReservedStackAccess
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
if (c != 0) {
// (Tips: if c != 0 and w == 0 then shared count != 0)
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires);
return true;
}
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
@ReservedStackAccess
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
@ReservedStackAccess
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null ||
rh.tid != LockSupport.getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) {
int c = getState();
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
} else if (readerShouldBlock()) {
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
} else {
if (rh == null) {
rh = cachedHoldCounter;
if (rh == null ||
rh.tid != LockSupport.getThreadId(current)) {
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)) {
if (sharedCount(c) == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
if (rh == null)
rh = cachedHoldCounter;
if (rh == null ||
rh.tid != LockSupport.getThreadId(current))
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
@ReservedStackAccess
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null ||
rh.tid != LockSupport.getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
--rh.count;
}
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
private static IllegalMonitorStateException unmatchedUnlockException() {
return new IllegalMonitorStateException(
"attempt to unlock read lock, not locked by current thread");
}
@ReservedStackAccess
final boolean tryWriteLock() {
Thread current = Thread.currentThread();
int c = getState();
if (c != 0) {
int w = exclusiveCount(c);
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
}
if (!compareAndSetState(c, c + 1))
return false;
setExclusiveOwnerThread(current);
return true;
}
@ReservedStackAccess
final boolean tryReadLock() {
Thread current = Thread.currentThread();
for (;;) {
int c = getState();
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return false;
int r = sharedCount(c);
if (r == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
if (compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null ||
rh.tid != LockSupport.getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return true;
}
}
}
// 当前线程是否是独占线程
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
// 返回当前持有独占锁的线程
final Thread getOwner() {
return ((exclusiveCount(getState()) == 0) ?
null :
getExclusiveOwnerThread());
}
// 当前读锁占有的数量
final int getReadLockCount() {
return sharedCount(getState());
}
// 当前是否有写锁
final boolean isWriteLocked() {
return exclusiveCount(getState()) != 0;
}
// 获取写锁占有数量
final int getWriteHoldCount() {
return isHeldExclusively() ? exclusiveCount(getState()) : 0;
}
// 获取当前线程持有读锁的数量
final int getReadHoldCount() {
if (getReadLockCount() == 0)
return 0;
Thread current = Thread.currentThread();
if (firstReader == current)
return firstReaderHoldCount;
HoldCounter rh = cachedHoldCounter;
if (rh != null && rh.tid == LockSupport.getThreadId(current))
return rh.count;
int count = readHolds.get().count;
if (count == 0) readHolds.remove();
return count;
}
// 反序列化
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
readHolds = new ThreadLocalHoldCounter();
setState(0); // reset to unlocked state
}
// 获得aqs的state
final int getCount() { return getState(); }
}
源码中一些常用方法标上了注释。
读锁的获取与释放
读锁获取解析
-
调用AQS的acquireShared(1)方法
-
AQS回调tryAcquireShared(1)方法
-
在tryAcquireShared方法中,流程如下
-
先检查写锁是否被其他线程获取,如果满足则返回-1,如果不满足执行步骤2
-
调用readerShouldBlock方法检查读是否应阻塞,如果返回true则调用fullTryAcquireShared(Thread.currentThread())方法
-
如果读不阻塞,则判断读锁的数量小于最大获取上限且使用CAS尝试加锁
-
如果CAS成功,进入计数器环节,返回1,如果失败则调用fullTryAcquireShared(Thread.currentThread())方法
-
计数器逻辑如下
- 读锁是第一次加锁,由firstReader记录当前线程,firstReaderHoldCount记录当前获取次数。
- 如果是首次加锁线程再次重入锁,则将firstReaderHoldCount++。
- 如果是其他线程先查看cachedHoldCounter是否是当前线程,如果count为0则缓存到readHolds中去,然后将count++,如果cachedHoldCounter不是则从readHolds中获取HoldCounter并将count++。
- readHolds为ThreadLocal变量通过ThreadLocalMap记录,ThreadLocal知识此处不详细说明。
count为0时为什么要缓存到readHolds中去,因为在释放读锁的时候count为0会从ThreadLoaclMap中删除,而此时是从cachedHoldCounter变量中缓存的值,可能不存在ThreadLocalMap中,所以需要先放入Map中,便于下次使用时值不会丢失。
-
如果第一次尝试读锁失败会调用fullTryAcquireShared进行加锁。
-
fullTryAcquireShared加锁流程如下,主要通过自旋加CAS加锁。
-
先检查写锁是否被其他线程获取,如果满足则返回-1,如果不满足继续执行
-
调用readerShouldBlock方法检查是否应阻塞,如果为true执行流程如下,否则执行步骤3
- 判断线程是否为第一个读锁线程,因为如果是第一个读锁线程那么count肯定不为0,所以执行步骤3
- 判断当前线程之前是否获取过读锁,如果未获取过(HoldCounter.count == 0),则返回-1,进入AQS等待队列排队,如果获取过则执行步骤3
- readerShouldBlock为什么满足还能继续获取共享锁?
由于当前线程是重入读锁的情况,可以无需排队继续获取。 - 为什么第一个读锁线程count肯定不为0?
由于在读锁释放时,如果是第一个读锁线程count为1释放则直接将第一个读锁线程置为null,如果是普通线程的HoldCounter.count == 1,则直接移除map中。 - 第一个读锁线程指firstReader中的值。
-
判断读锁是否已获取上限,如果获取上限抛出Error
-
使用CAS加锁,如果CAS竞争失败则重新自旋判断,如果加锁成功进入计数器环节,返回1,
-
计数器逻辑和tryAcquireShared方法中一致
读锁的数量很多,所以返回值通过-1表示无法获取读锁,1表示能获取读锁,在AQS中我们讲这个方法的返回值大于0时为剩余读锁数量,此时的返回值并未按通常情况来,因为2^16 - 1的数量大部分情况下不会上限所以通过1来表示还能够获取读锁。
-
-
-
如果tryAcquireShared小于0,在AQS中调用doAcquireShared方法入队并阻塞,此处不详细讲解。
读锁释放解析
- 调用AQS的releaseShared(1)方法
- AQS回调tryReleaseShared(1)方法
- 在tryReleaseShared方法中,流程如下
- 计数器逻辑如下
-
如果是firstReader为当前线程,则直接操作firstReaderHoldCount–,如果firstReaderHoldCount==1时将firstReader置为null。
注意:firstReader在第一次读锁获取时设置值,但不代表他将在这批次读锁完全释放后才会置为null,也就是同批次读锁获取时该值可能为null。
-
如果firstReader不为当前线程则从cachedHoldCounter中先判断是否为当前线程,如果是当前线程则执行步骤4
-
判断cachedHoldCounter的值是否为当前线程,否则从readHolds中获取当前线程的HoldCounter
-
判断当前线程的HoldCounter.count是否等于1,如果是则从readHolds中remove掉,如果不是判断是否小于等于0,如果小于等于0抛出unmatchedUnlockException异常。
-
将HoldCounter.count–
-
- CAS+自旋设置state值
- 获取当前state值,并将state值CAS设置为state - SHARED_UNIT(1 <<< 16),如果CAS成功则判断新state值,如果为0返回true,否则返回false。
- 如果CAS竞争失败则进入下次自旋。
- 计数器逻辑如下
- 如果tryReleaseShared返回true,表明释放读锁成功,在AQS中调用doReleaseShared方法唤醒等待队列节点,此处不详细讲解。
写锁的获取和释放
写锁的获取流程
- 调用AQS的acquire(1)加锁
- AQS回调tryAcquire(1)方法
- tryAcquire(1)方法执行流程如下
-
判断当前state值不为0,如果为true流程如下,否则执行步骤2
- 如果写锁为0,则说明当前存在读锁,但不存在写锁,则tryAcquire整体返回false。
- 如果写锁不为0,判断是否是当前线程持有写锁,如果不是,则tryAcquire整体返回false。
- 如果是当前线程持有写锁表示重入写锁,判断重入后写锁不超过上限,如果超过上限则抛出Error。然后直接setState值,tryAcquire整体返回true。
为什么重入写锁时不使用CAS设置?
因为写锁线程同时只有一个线程持有,在重入时,只有当前线程会对次进行修改,其他线程无法获取写锁和读锁,所以不会发生竞争,无需使用CAS设置。
-
调用writerShouldBlock方法,如果返回true,则tryAcquire整体返回false。
-
如果writerShouldBlock方法返回false,尝试使用CAS加锁,如果CAS竞争失败,则tryAcquire整体返回false。
-
如果CAS成功,则设置写锁线程为当前线程,返回true。
-
- 如果tryAcquire方法返回true则表示获取写锁成功,否则在AQS中执行入队逻辑。此处不详细讲解。
写锁的释放流程
-
调用AQS的release(1)释放写锁
-
AQS回调tryRelease(1)方法
-
tryRelease(1)方法执行流程如下
- 判断当前线程是否持有写锁,如果未持有写锁则抛出IllegalMonitorStateException异常。
- 然后将state-1做为新的state值,当新state值为0时,将写锁线程置为null,并且更新state值,返回true
- 如果新state值不为0,更新state值,返回false。
此时设置新state值时,无需使用CAS设置,因为同时只有一个线程能持有写线程。
-
如果tryRelease方法返回true则表示写锁释放成功,在AQS中执行唤醒等待节点逻辑。此处不详细讲解。
公平锁和非公平锁的实现
writerShouldBlock方法
- 公平锁下的实现方式
调用的是AQS中的hasQueuedPredecessors方法,判断队列中是否还有等待节点,如果有等待节点表示需要阻塞。
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
- 非公平锁下的实现方式
在非公平锁的实现下,写操作做为一个互斥操作在读写锁中是一个重量级操作,所以在非公平锁的实现下不阻塞。
final boolean writerShouldBlock() {
return false; // writers can always barge
}
readerShouldBlock方法
- 公平锁下的实现方式
调用的是AQS中的hasQueuedPredecessors方法,判断队列中是否还有等待节点,如果有等待节点表示需要阻塞。
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
- 非公平锁下的实现方式
在非公平锁的实现下,写操作做为一个互斥操作在读写锁中是一个重量级操作,所以在非公平锁的实现下对于读请求,如果等待队列首部操作是写操作,则发生阻塞,尽量让写操作不会等待太久。
final boolean readerShouldBlock() {
// 作为避免无限写入器饥饿的启发式方法,如果暂时显示为队列头的线程(如果存在)是等待写入器,则阻塞。这只是一种
// 概率效应,因为如果在其他尚未从队列中耗尽的已启用读取器后面有等待写入器,则新读取器不会阻塞
return apparentlyFirstQueuedIsExclusive();
}
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
总结:对于公平锁的实现下,只要等待队列中存在节点则需要阻塞等待,对于非公平锁的实现下,写操作永不等待,读操作下如果等待队列中操作为写,会阻塞让写操作优先执行,可以说是一种贪心做法,让写操作尽可能的少等待多执行,因为写操作和其他操作是互斥的。
总结
读写锁逻辑:
- 当有一个线程获取写锁后,其他线程无法获取写锁和读锁,持有写锁线程可以同时获取读锁。
- 当有一个线程获取读锁后,其他线程(包括自己线程)无法获取写锁,可以获取读锁。
- 该类的特别之处就在用使用一个int值通过分组将读锁和写锁区分。
CountdownLatch类
类介绍
一种提供了允许一个或多个线程同时等待的同步屏障。当屏障被消耗后则不会在阻塞任何线程。
内部子类Sync
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c - 1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
该源码仅实现了共享锁的释放和解锁,和读写锁中读锁的逻辑不同,tryAcquireShared默认逻辑应该是获取共享锁,此时被设计成了判断共享锁是否被全部释放,tryReleaseShared默认逻辑是释放共享锁成功后返回true,此时被设计成了当所有共享锁被释放后返回true。而这两点不同构成了CountDownLatch的逻辑。
运行流程
- 构造方法
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
初始化时,默认构建count层屏障,每次countDown方法会减少一次屏障,当屏障为0时,await方法会不阻塞返回,当屏障不为0时,await方法调用后阻塞。
- await方法
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
调用AQS中可响应中断的获取共享锁方法,该方法和acquireShared区别。acquireSharedInterruptibly方法当检查到中断直接抛出中断异常,而acquireShared检查到中断记录,当成功获取锁并返回时重新中断线程。
- 调用AQS中acquireSharedInterruptibly方法
- 回调自己实现的tryAcquireShared方法为return (getState() == 0) ? 1 : -1;
- 当返回1时,按照AQS的逻辑是成功获取了共享锁直接返回,而按照CountDownLatch的思想是屏障被消耗掉了不阻塞直接返回。
- 当返回-1是,按照AQS的逻辑是未获取到共享锁阻塞当前线程,而按照CountDownLatch的思想是屏障未被消耗到0,需要阻塞。
- 当被阻塞的线程需要等待unPark的到来才能脱离阻塞。
- countDown方法
public void countDown() {
sync.releaseShared(1);
}
- 调用AQS中的releaseShared方法
- 回调自己实现的tryReleaseShared方法
- 通过自旋+CAS方式设置值
- 如果读取到的值为0则返回false
- 否则通过CAS自减,返回值为自减后的值是否为0
- 如果tryReleaseShared返回true,则唤醒等待队列中节点,由于是共享锁会传播,所以会唤醒所有等待队列中的共享节点。这块是AQS共享锁解锁的流程,此处不详细赘述。
- 如果tryReleaseShared返回false,则什么也不做。
总结
CountDownLatch类是借助AQS的共享锁实现的同步屏障,初始化时共享锁数量不为0,当调用await方法时,实际是获取共享锁的过程,由于tryAcquireShared方法返回false,所以进入AQS等待队列阻塞,直到有n个线程调用了countDown方法,实际上是释放共享锁的线程,当最后一次释放共享锁时,tryReleaseShared方法返回true,唤醒等待队列中节点,同步屏障失效,之后所有await调用tryAcquireShared方法都将返回true。
由tryReleaseShared方法规定,如果state值为0则永不自减,所以tryAcquireShared可以只判断==0。
tryAcquireShared和tryReleaseShared的返回值含义大体上已经被AQS规定好了,但内部实现可有所不同。
AQS规定tryAcquireShared返回大于等于0表示成功获取共享锁,小于0表示获取共享锁失败。
AQS规定tryReleaseShared返回true则表示唤醒后续等待节点,返回false则什么也不做。
由于state值只在构造方法中初始化,所以该类的同步屏障只可使用一次,当state值归为0后则永远为0。
CyclicBarrier类
类介绍
一种提供了允许一个或多个线程同时等待的同步屏障。当屏障被消耗后会初始化进入下一流程。可以理解为CountDownLatch的可复用版本。
类原理
该类并未使用内部子类Sync继承自AQS的方法实现,而是使用ReentrantLcok加ConditionObject的方式实现的。
运行流程
调用await方法进入等待,当等待线程数达到n个时,释放所有线程,开启下一流程。
-
初始化一个CyclicBarrier类
public class CyclicBarrier { // 内部类,作用是区分线程属于哪批流程 private static class Generation { Generation() {} // prevent access constructor creation boolean broken; // initially false } // 独占锁 private final ReentrantLock lock = new ReentrantLock(); // 条件队列 private final Condition trip = lock.newCondition(); // 每批次人数 private final int parties; // 每批次末尾运行代码 private final Runnable barrierCommand; // 当前流程 private Generation generation = new Generation(); // 当前还需等待线程,parties - count的值为当前已阻塞线程 private int count; }
类主要属性如上,count每一流程中都会从parties变为0后开启一下流程,或者调用breakBarrier方法强行中断当前流程。通过独占锁将各线程串行化从而避免并发问题。
-
await方法如下
private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException { final ReentrantLock lock = this.lock; lock.lock(); try { final Generation g = generation; if (g.broken) throw new BrokenBarrierException(); if (Thread.interrupted()) { breakBarrier(); throw new InterruptedException(); } int index = --count; if (index == 0) { // tripped boolean ranAction = false; try { final Runnable command = barrierCommand; if (command != null) command.run(); ranAction = true; nextGeneration(); return 0; } finally { if (!ranAction) breakBarrier(); } } for (;;) { try { if (!timed) trip.await(); else if (nanos > 0L) nanos = trip.awaitNanos(nanos); } catch (InterruptedException ie) { if (g == generation && ! g.broken) { breakBarrier(); throw ie; } else { Thread.currentThread().interrupt(); } } if (g.broken) throw new BrokenBarrierException(); if (g != generation) return index; if (timed && nanos <= 0L) { breakBarrier(); throw new TimeoutException(); } } } finally { lock.unlock(); } }
该类重载了多个await方法,最后都会调用上述dowait方法执行逻辑,区别为dowait两个参数不同。
参数1:boolean timed 表示是否是限时阻塞,如果为true表示限时,false为无限阻塞。
参数2:long nanos 表示限时阻塞时间,单位为纳秒(Nanos)。如果timed值为false时,该值无效。
执行流程如下- 先获取独占锁
- 获取当前流程generation
- 判断流程是否被中断,如果中断则抛出BrokenBarrierException
- 判断线程是否被中断,如果中断则调用breakBarrier方法中断当前流程并抛出InterruptedException
- 自减count值,如果值为0则执行如下流程,否则调用步骤6
- 获取barrierCommand属性
- 调用barrierCommand的run方法,注意此刻是调用run方法不是启动一个线程。
- ranAction变量用于判断是否在run方法中发生了异常,如果发生了异常则在finally块处理,进行中断流程,进行完成后当前由于发生异常所以会调用最外层finally释放独占锁
- 如果未发生异常则调用nextGeneration进行下一流程并返回0.
- 自旋进入,判断是否限时,如果不限时则调用ConditionObject的await方法阻塞。
- 如果限时则调用awaitNanos限时阻塞,在阻塞过程中发生中断异常则判断当前流程是否结束,如果未结束则中断当前流程抛出异常,如果结束则中断当前线程
- 当从Condition的await中唤醒时,判断当前流程是否被中断,如果中断抛出BrokenBarrierException
- 判断当前流程是否一致,如果不一致说明当前线程流程已结束,则返回阻塞时还剩余多少线程未到。
- 如果一致则表示提前被唤醒,所以通过自旋重新进入阻塞。
- 判断当限时等待时且等待时间小于等于0时中断当前流程,抛出TimeoutException
- 方法无论是抛出异常还是return返回都将释放独占锁
由于await方法返回时需要获取独占锁,所以通过判断流程不同来返回,因为nextGeneration方法执行时也持有独占锁,只有当方法结束时才会释放独占锁,所以不会导致被signal后流程还相同的情况。
-
nextGeneration方法如下
private void nextGeneration() { trip.signalAll(); count = parties; generation = new Generation(); }
该方法是开启一个新的流程,调用该方法时,必须持有独占锁。
该方法有两处调用一个是CyclicBarrier类的reset方法,一个是count==0时调用。
该方法会唤醒trip的条件队列中的节点,并重置count为初始值,并启动一个新的流程。 -
breakBarrier方法如下
private void breakBarrier() { generation.broken = true; count = parties; trip.signalAll(); }
该方法是中断当前流程,通过broken属性,调用该方法时,必须持有独占锁。
该方法会唤醒trip的条件队列中的节点,并重置count为初始值。重新开始这次流程。
总结
该类使用ReentrantLock类和ConditionObject类保证所有线程顺序执行,无并发问题。
笔者猜测因为独占锁释放时会写一个volatile变量,通过Happes-Before原则来保证该类中所有属性无须volatile保证可见性。
Semaphore类
类介绍
一种提供了允许一个或多个线程同时获取共享锁的类,通常用于对于某些共享资源设置同时操作的线程数控制。
内部子类Sync
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933L;
// 共享锁数量初始化后不可变
Sync(int permits) {
setState(permits);
}
final int getPermits() {
return getState();
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
final void reducePermits(int reductions) {
for (;;) {
int current = getState();
int next = current - reductions;
if (next > current) // underflow
throw new Error("Permit count underflow");
if (compareAndSetState(current, next))
return;
}
}
final int drainPermits() {
for (;;) {
int current = getState();
if (current == 0 || compareAndSetState(current, 0))
return current;
}
}
}
// 非公平锁
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L;
NonfairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
// 公平锁
static final class FairSync extends Sync {
private static final long serialVersionUID = 2014338818796000944L;
FairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
默认实现为非公平锁实现,公平锁和非公平锁区别和其他类一致,公平锁获取时会检查阻塞队列是否有有效节点,如果有则直接排队。非公平锁则会插队。
reducePermits方法为提供一种在运行时减少共享锁的方法
drainPermits方法为提供一种将当前未分配出去的共享锁置空的方法
运行流程
- 共享锁获取流程
- 通过自旋加CAS方式减少state的值,并返回当前剩余共享锁数量,如果为负数表示共享锁无法获取。
- 当剩余共享锁数量小于0时,不会尝试CAS而是直接返回负值。
- 共享锁释放流程
- 通过自旋加CAS方式增加state的值,如果新值小于旧值则抛出Error。
- 通过CAS设置成功后返回true,否则继续自旋
总结
一个标准的共享锁实现方案。初始化后共享锁数量不可增加,但是可以减少,通过drainPermits和reducePermits方法减少。