AQS(一)
目录:
锁
说到锁会联想到悲观锁、乐观锁、公平锁、非公平锁等等。在java中的锁可以分为隐式锁和显示锁。 * 隐式锁说的是synchronized,当我们在方法上或者代码块中使用了synchronized关键字就会自动帮我们加锁,当程序执行完毕或者出现异常时会自动释放锁; * 显示锁,指的是Lock,这类锁需要我们程序员主动调用lock();方法来加锁,unlock();方法来解锁 这篇文章主要介绍的是显示锁。AQS基本介绍
AQS是JUC的基础,可以这么说AQS是面向锁的,锁是面向程序员的。我们平时用的ReentrantLock、CountDownlatch等等这些是锁,而这些锁是在AQS的基础上实现的。 对于AQS可以拆分成以下几个部分来说:- 成员变量
- 不可变方法
- 模板方法
- 需要重写的方法
- 节点
- ConditionObject
成员变量
AQS中包含这么几个成员变量:state、head、tail * state:一个int型变量,对于独占锁代表的是获取锁的次数;对于共享锁代表的是还能获取锁的次数 * head:Node类型变量,在同步队列中代表的是头节点。同步队列中头结点相当于一个哨兵节点,并无实际含义 * tail:Node类型变量,在同步队列中代表的是尾结点。节点
Node是AQS的一个内部类,同步队列和等待队列使用的都是该Node,只不过同步队列是双向队列,等待队列是单向队列。 对于同步队列来说,Node中存放的是没有获取到锁的线程;对于等待队列来说,Node中存放的是调用了condition.await()方法的线程 Node中有个waitStatus变量表示的是当前节点(线程)所处的状态,该状态决定了该节点是否有获取锁的资格。主要有以下几个状态:CANCELLED:该值为1,如果节点处于该状态,说明该节点是由于超时或者中断导致的。如果处于这个状态说明是要放弃获取锁,如果进入了该状态说明就无法进入其他状态了
This node is cancelled due to timeout or interrupt.Nodes never leave this state. In particular,a thread with cancelled node never again blocks.
SIGNAL:该值为-1,该节点的后继节点处于挂起或者即将挂起的状态,如果当前节点(线程)释放了锁或者取消了锁并且状态是SIGNAL,那么就会尝试唤醒后继节点
The successor of this node is (or will soon be) blocked (via park), so the current node must unpark its successor when it releases or cancels. To avoid races, acquire methods must first indicate they need a signal,then retry the atomic acquire, and then,on failure, block.
CONDITION:该值为-2,当前节点处于一个等待队列,在转换之前不能用到同步队列中。
This node is currently on a condition queue. It will not be used as a sync queue node until transferred, at which time the status will be set to 0. (Use of this value here has nothing to do with the other uses of the field, but simplifies mechanics.)
PROPAGATE:该值为-3,释放共享资源时要通知其他节点
A releaseShared should be propagated to other nodes. This is set (for head node only) in doReleaseShared to ensure propagation continues, even if other operations have since intervened.
还有一个0,初始状态
不可变方法
* getState():获取state * setState():设置state * campareAndSetState():使用CAS方式设置state需要重写的方法
* tryAcquire():尝试获取锁,默认抛出UnsupportedOperationException异常 * tryRelease():尝试释放锁,默认抛出UnsupportedOperationException异常 * tryAcquireShared():尝试获取共享锁,默认抛出UnsupportedOperationException异常 * tryReleaseShared():尝试释放共享锁,默认抛出UnsupportedOperationException异常 * isHeldExclusively():释放独占,默认抛出UnsupportedOperationException异常 如果子类使用以上几个方法需要重写,不然会抛出异常,而上面的前四个方法是在模板方法中使用模板方法
AQS提供了几个模板方法让其子类在获取锁或者释放锁时重写。比如对于独占锁,子类需要调用AQS中的acquire(int arg)方法来获取锁,在acquire方法中就会调用子类重载的tryAcquire方法,判断是否符合自己的逻辑。acquire
该方法针对独占锁,当子类调用该方法时(比如ReentrantLock),会有如下代码逻辑:public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
想要获取一个独占锁需要调用acquire方法,首先调用子类实现的tryAcquire方法,如果返回false,接着调用acquireQueued(addWaiter(Node.EXCLUSIVE), arg))。在该方法中首先构造一个EXCLUSIVE类型的节点,将该节点加入到队列中,然后在同步队列中循环获取锁。
添加同步队列:addWaiter
构造一个独占锁的节点,将该节点添加到同步队列中。调用方法:addWaiter(Node.EXCLUSIVE); 该方法具体实现如下:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
- 首先将构造的独占锁(共享锁)节点和当前线程创建一个新的节点node
- 将tail赋值给pred判断pred是否为null
- 如果为null调用enq添加节点
- 如果不为null,将node的前驱节点指向该pred,使用CAS方式将node添加到尾结点
- 如果添加成功,将pred的后继节点指向node,并返回该node。这样的话pred和新添加的尾结点就构成了一个双向链表
- 如果添加失败,调用enq继续添加
enq添加节点
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
从上面代码可以看出enq是一个死循环,除非添加成功,不然就无法跳出该循环。
- 将tail赋值给t
- 如果t为null,说明该同步队列没有任何节点
- 使用CAS的方式构造头结点,该头结点相当于一个哨兵节点,没有任何含义。将头结点赋值给tail,进入下一次循环
- t不为null,有两种可能:1、可能是上次添加头结点成功之后再次进入的循环。2、可能是在addWaiter方法中添加节点失败
- 将node的前驱节点指向t
- 使用CAS的方式将node添加到尾结点
- 添加成功,将t的后继节点指向node,并返回t
- 添加失败,进入下次循环继续添加,直至添加成功
以上就是添加节点addWaiter的逻辑,当添加节点到同步队列成功,接下来就是从同步队列中获取节点(线程)执行任务
获取队列:acquireQueued
该方法也是一个死循环,判断当前节点的前驱节点是否是头结点,如果是头结点,将该节点设置为head,并且将该节点中的thread设置为null,具体代码如下:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
- 设置失败标failed为true,中断标志interrupted为false
- 进入无限循环,获取当前节点的前驱节点p
- 如果p为head并且调用子类的tryAcquire成功,将该node设置为head,将failed设置为false并返回interrupted
- 如果p不为head或者子类调用tryAcquire失败,则判断该node是否需要阻塞,如果需要阻塞将该node进行挂起,并设置interrupted为true,进入下一次循环
- 当跳出for循环进入finally,判断failed是否为false,如果想让failed为true,可以是抛出中断异常,那么在for循环中就不会进入if条件,failed也就不会改为false。这时,finally中cancelAcquire的代码块就会执行
shouldParkAfterFailedAcquire
如果p不为head或者子类调用tryAcquire失败,则判断该node是否需要阻塞,具体代码逻辑如下:
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 {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
- 获取前驱节点的waitStatus:ws
- 如果ws为SIGNAL,返回true,说明该节点需要挂起
- 如果ws大于0,说明该前驱节点的状态CANCEL,是放弃获取锁的,那么就进入while循环在同步队列中往前查找,直至找到一个非CANCEL状态的节点,并将该节点的后继节点执行node。
- 如果ws小于0,那么就使用CAS方式将该node节点的前驱节点pred的状态设置为SIGNAL,acquireQueued下次循环的时候进入该方法就会在第一个if条件直接返回true,那么就会将该节点挂起
parkAndCheckInterrupt
判断了如果该节点是需要挂起的,那么就会调用该方法进行挂起并且会检查中断,对于这个中断来说,acquire方法是不支持中断的,只是会将中断标志位置为true。具体代码如下:
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
挂起这块代码就很简单,调用LockSupport#park进行挂起,并返回线程的中断标志
以上就是acquire方法的整体逻辑,当无法获取锁的时候会将该节点(线程)加入到同步队列,然后再从同步队列中获取节点执行线程任务。
release
当线程执行完毕之后就会释放锁,让下个线程获取锁继续执行任务。释放锁调用的是release方法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方法
- 如果返回false说明释放锁失败,直接返回false
- 如果返回true说明释放锁成功,判断头结点是否为null并且头结点的waitStatus不等0(不等于0说明头同步队列中有节点(线程))
- 调用unparkSuccessor通知后继节点
unparkSuccessor
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
- 得到节点node的waitStatus状态ws
- 如果ws小于0,说明该节点的状态不是CANCEL,那么就使用CAS方式将该节点的状态改为0
- 获取节点node的下一个节点s
- 如果s为null或者s的状态大于0,说明该节点无效,那么就从尾结点往前查找,一直找到node节点为止,找到一个离node节点最近的并且waitStatus小于0的节点赋值给s
- 如果s不为null,则将该节点的线程解挂
以上就是独占锁的加锁和释放锁逻辑,这两个方法是不支持中断的!
acquireInterruptibly
该方法也是独占锁的获取锁逻辑,只不过该方法是支持中断操作的。 ```java public final void acquireInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (!tryAcquire(arg)) doAcquireInterruptibly(arg); } ```- 首先判断线程的中断标志,如果为true,抛出InterruptedException中断异常,程序结束
- 如果为false,调用子类自己实现的tryAcquire方法进行锁的释放,如果释放失败调用doAcquireInterruptibly
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
该方法和acquireQueued方法大体相同,区别在于:判断是否需要挂起,如果需要挂起,则将node进行挂起并返回线程的中断状态,如果返回true,则抛出InterruptedException异常,此时就会进入finally代码中的cancelAcquire方法的逻辑
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;
node.waitStatus = Node.CANCELLED;
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
如果抛出了异常,进入该方法的话,那么就会将该节点的状态设置为CANCELLED。
- 如果node为null,直接返回
- 获取node的前驱节点pred,判断pred的的waitStatus是否大于0,如果大于0,说明前驱节点的状态为CANCELLED,那么就循环往前查找,一直找到一个waitStatus小于0的节点
- 将node的waitStatus设置为CANCELLED
- 如果node为tail并且将pred设置为尾结点成功,则调用compareAndSetNext,使用CAS的方式将pred的后继节点设置为null
- 否则说明node不是tail或者CAS设置失败
- 如果node不是head的后继节点 && (pred的waitStatus是SIGNAL || 使用CAS方式设置pred的waitStatus的状态为SIGNAL成功) && pred节点的线程不为null,那么使用CAS的方式设置pred.next指向node.next,将node的后继节点指向自己
- 如果以上条件不成立,则调用unparkSuccessor方法,唤醒node的后继节点,将node的后继节点指向自己
acquireShared
该方法是用于获取共享锁,大致原理是给state变量设置一个初始值,每当一个线程获取锁时,state减一,直至为0。如果小于0,那么就会进入同步队列 ```java public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); } ``` * tryAcquireShared方法由其子类实现,如果小于0,说明该节点(线程)没有获取到锁 * 如果没有获取到锁,将该节点放入同步队列doAcquireShared
该方法和独占锁的大致相同,不同点在于:
- 调用addWaiter时构造的节点类型是SHARED
- 如果node的前驱节点是头结点,并且再次调用子类的tryAcquireShared方法的实现成功,则调用setHeadAndPropagate方法
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);
if (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方法除了和独占锁一样会调用setHead方法设置头结点以外,还会根据传播状态判断是否需要唤醒后继节点
releaseShared
共享锁的释放:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
- 如果释放锁成功,调用doReleaseShared方法并返回true
- 如果释放锁失败,直接返回false
doReleaseShared
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
- 将head赋值给h
- 如果h不为null && h 不是尾结点tail
- 获取h的waitStatus状态ws
- 如果ws是SIGNAL,则使用CAS的方式将h从SIGNAL状态改为0,如果修改成功调用unparkSuccessor方法唤醒后继节点,如果失败继续for循环
- 如果ws是0 && 使用CAS的方式将h的waitStatus从0修改为PROPAGATE,如果不成功继续for循环
- 如果h是head节点,直接跳出for循环
acquireSharedInterruptibly
该获取共享锁的方法是支持中断操作的
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
- 首先判断线程的中断标志位,如果为true,直接抛出中断异常
- 调用子类实现的tryAcquireShared方法,如果小于0,则调用doAcquireSharedInterruptibly方法将节点加入到同步队列中
doAcquireSharedInterruptibly方法和上面说的doAcquireShared方法一样,只不过doAcquireSharedInterruptibly方法是支持中断的,如果发生中断,那么就会进入finally代码块中的cancelAcquire方法
tryAcquireNanos
这是一个支持超时时间并且可中断的独占式获取锁的方法
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
- 判断线程的状态,如果中断标志位为true,则抛出异常
- 调用子类实现tryAcquire,如果获取锁成功直接返回,如果失败,调用doAcquireNanos方法,将node加入到同步队列中,并传入超时时间
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
该方法和独占式的acquireQueued方法基本一致,区别在于超时时间的计算
- 如果传入的超时时间小于等于0,返回false
- 用当前时间 + 超时时间作为deadline
- 获取node的前驱节点p,如果p是head并且获取锁成功则返回true
- 用deadline减去当前时间,计算出还剩多少时间,如果小于等于0,则返回false
- 判断该node是否需要阻塞并且剩余是时间需要大于1000纳秒,如果成立则调用LockSupport.parkNanos方法进行阻塞
- 判断线程的中断状态,如果为true,抛出异常
- 进入finally代码块
这里需要注意的是在判断是否需要阻塞的时候还有一个条件是剩余的超时时间必须大于1000纳秒,因为1000纳秒很短,为了精准控制超时时间,如果小于1000纳秒,就直接进入无限死循环
tryAcquireSharedNanos
该方法是获取共享锁,支持超时时间和中断操作public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquireShared(arg) >= 0 ||
doAcquireSharedNanos(arg, nanosTimeout);
}
- 如果线程的中断标志位为true,则抛出异常
- 如果获取共享锁成功,则直接返回
- 如果失败,则调用doAcquireSharedNanos方法,将node放入同步队列中
doAcquireSharedNanos
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
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
failed = false;
return true;
}
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
该方法和doAcquireShared基本一致,区别在于超时时间的计算,而超时时间的计算和独占锁是一样的,这里就不再细说
条件队列
在使用synchronized关键字时,我们使用的是wait方法使正在执行的线程进入等待,调用notify或者notifyAll方法将正在等待的线程唤醒。而在AQS中如果想让一个线程进入等待,那么需要调用await方法,唤醒线程使用的是signal或者signalAll方法,而这些方法都是和Condition有关,Condition是和Lock相关的,如果想要获取一个Condition对象,那么首先需要获取锁,然后通过锁获取Condition对象。
当一个Condition对象调用了await方法时,实际是调用了AQS的内部类ConditionObject的await方法。一个Condition对象对应一个ConditionObject。而一个ConditionObject维护了一个单向队列
await
通过Lock的实现类调用await方法最终会进入ConditionObject#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);
}
- 首先判断线程的中断标志位,如果为true,则抛出中断异常
- 调用addConditionWaiter方法构造一个Node.CONDITION类型的节点
- 调用fullyRelease方法,释放当前线程所占用的所有的锁(重入锁的可能)
- 设置中断模式interruptMode为0
- 在while循环中判断构造的CONDITION类型的节点是否在同步队列中isOnSyncQueue
- 如果不在同步队列中则调用LockSupport.park方法,挂起当前线程,线程阻塞
- 调用checkInterruptWhileWaiting方法检查线程被唤醒的原因,并且使用interruptMode记录中断状态。(到这一步说明构造的CONDTION类型的node被其他线程唤醒或者被中断唤醒)
- 如果是中断状态,则说明该node被添加到了同步队列中
- 到了这一步说明该node已经被添加到同步队列中了,调用acquireQueued方法自旋式获取锁,需要将之前在释放锁的state值原封不动的传递进去。如果在获取锁的过程中被中断,并且之前的中断模式不是THROW_IE(可能是0),那么设置中断模式为REINTERRUPT,即表示在调用signal或者signalAll方法之后设置的中断状态
- 此时已经获取到了锁,但是如果线程是 在调用signal或者signalAll方法之前就因为中断而被唤醒 的情况时,将结点添加到同步队列的的时候,并没有清除在条件队列中的结点引用。因为,需要判断node.nextWaiter是否不为null,如果是则还需要从条件队列中移除彻底移除这个结点。
- 如果中断模式interruptMode不为0,那么调用reportInterruptAfterWait方法对不同的中断模式做出处理
以上就是await方法的整体逻辑,再说几个在这个方法中调用的方法:
addCondtionWaiter
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
- 将尾结点赋值给t
- 如果t不为null并且t的waitStatus不是CONDTION,那么就调用unlinkCancelledWaiters方法清除所有不在等待状态的节点,然后再将lastWaiter赋值给t
- 使用当前线程构造一个CONDITION类型的node
- 如果t为null,则将node赋值给firstNode;如果不为null,则将t的下一个节点指向node
- 将node赋值给lastWaiter并返回新创建的node
fullyRelease
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
- 获取state的值,这里的state不一定是1,因为可能有重入锁
- 调用release方法将所持有的所有的锁释放,这里为什么不用CAS或者死循环,因为调用了await方法的线程一定是获取了锁的线程
- 释放成功返回获取到的state值
isOnSyncQueue
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null) // If has successor, it must be on queue
return true;
return findNodeFromTail(node);
}
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
- 如果状态为CONDITION或者node的前驱节点为null,说明肯定不在同步队列中(同步队列中节点状态不可能为CONDITION;将一个节点添加到同步队列中时,首先设置的就是前驱节点)
- 如果node的后继节点不为null,说明肯定在同步队列中,而且还不是尾结点(因为在等待队列中该node是尾结点,没有后继节点)
- 有可能状态不为Node.CONDITION并且node.prev的值不为null,此时还没彻底添加到队列中,但是不知道后续会不会添加成功,因为enq入队时CAS变更的tail可能失败。此时需要调用findNodeFromTail从后向前遍历整个同步队列,查找是否有该结点
checkInterruptWhileWaiting
private static final int REINTERRUPT = 1;
private static final int THROW_IE = -1;
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
final boolean transferAfterCancelledWait(Node node) {
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
enq(node);
return true;
}
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
检查中断以及被唤醒的原因,这个方法中涉及到两个成员变量:
- REINTERRUPT:退出await方法之前,需要设置中断状态,由于此时已经获取了锁,相当于设置一个标记位。如果是在调用signal或者signalAll方法之后被中断,会是这个模式
- THROW_IE:退出await方法之前,需要抛出InterruptedException异常。如果是在调用signal或者signalAll之前就被中断了,会是这个模式
- 如果Thread是中断标志位为true,则调用transferAfterCancelledWait方法判断是在什么时候中断的。如果是在调用signal或者signalAll之前被中断的,那么返回THROW_IE,表示在await方法最后会抛出异常;如果是在调用signal或者signalAll方法之后被中断的,则返回REINTERRUPT,表示在await方法最后会重新设置中断状态
- 如果为false,则返回0
transferAfterCancelledWait
- 在说这个方法之前需要知道的一点:signal或者signalAll方法中会将结点状态从Node.CONDITION设置为0
- 使用CAS的方法将node的状态从CONDITION改为0,如果成功说明该节点肯定是在signal或者signalAll方法被调用之前 被中断而唤醒的
- 设置成功,调用enq方法将该node添加到同步队列中。但这里并没有把该node从等待队列中移除。所以才有了最后的unlinkCancelledWaiters方法,移除被取消的线程
- 否则,表示是在signal或者signalAll被调用之后,又被设置了中断状态的signal或者signalAll的方法中会将结点添加到同步队列中,这里循环判断到底在不在队列中,因为或者signal或者signalAll方法可能还没有执行完毕,这里等它执行完毕,然后返回false,如果不等他执行完毕,在回到外面的await方法中时,可能会影响后续的重新获取锁acquireQueued方法的执行
- 确定被添加到同步队列,返回false
unlinkCancelledWaiters
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
Node trail = null;
while (t != null) {
Node next = t.nextWaiter;
if (t.waitStatus != Node.CONDITION) {
t.nextWaiter = null;
if (trail == null)
firstWaiter = next;
else
trail.nextWaiter = next;
if (next == null)
lastWaiter = trail;
}
else
trail = t;
t = next;
}
}
这个方法就比较简单了,就是遍历整个单链表,清除所有取消等待的节点
reportInterruptAfterWait
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
对中断模式进行处理
signal
当线程在等待队列中,如果想要唤醒这个线程除了被中断以外,还有就是调用signal或者signalAll方法。一个是通知单个线程,一个是通知所有线程public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
- 首先判断是否是持有锁的线程,不是的话直接抛出异常
- 将首节点赋值给first,如果first不为null,调用doSignal方法唤醒线程
doSignal
从头结点往后遍历,唤醒等待时间最长的节点,并清理一些已经取消等待的节点
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
- firstWaiter指向first的后继结点,并且如果为null,则lastWaiter也置为null,表示条件队列没有了结点
- first的后继引用置空,这样就将first出队列了
- 循环条件
- 调用transferForSignal转移结点,如果转移失败(结点已经取消等待了);
- 则将first赋值为它的后继,并且如果不为null;
- 满足上面两个条件,则继续循环
transferForSignal
尝试将遍历到的结点转移至同步队列中,调用该方法之前并没有显示的判断结点是不是处于等待状态,而是在该方法中通过CAS的结果来判断。
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
- 尝试将结点的等待状态变成0,表示取消等待.如果更改等待状态失败,那说明一定是原本就不是Node.CONDITION状态,表示结点早已经取消等待了,则返回false。这里不存在并发的情况,因为调用线程此时已经获取了独占锁
- 调用enq方法将结点添加到同步队列尾部,返回添加结点的前驱结点
- 获取前驱结点的状态ws
- 如果ws大于0 表示前驱已经被取消了 或者 将ws改为Node.SIGNAL失败,表示前驱可能在此期间被取消了则调用unpark方法唤醒被转移结点中的线程,好让它从await中的等待唤醒(后续尝试获取锁)否则,那就由它的前驱结点获取锁之后释放锁时再唤醒。
Condition的应用
有这样一个需求:开启三个线程,顺序打印ABCABCABC...public class ConditionABC {
static Lock lock = new ReentrantLock();
static Condition conditionA = lock.newCondition();
static Condition conditionB = lock.newCondition();
static Condition conditionC = lock.newCondition();
static volatile int flag = 1;
public void printA(){
try {
lock.lock();
while (flag != 1){
try {
conditionA.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("A");
flag = 2;
conditionB.signal();
}finally {
lock.unlock();
}
}
public void printB(){
try {
lock.lock();
while (flag != 2){
try {
conditionB.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("B");
flag = 3;
conditionC.signal();
}finally {
lock.unlock();
}
}
private void printC(){
try{
lock.lock();
while(flag != 3){
try {
conditionC.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("C");
flag = 1;
conditionA.signal();
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
A a = new A(new ConditionABC());
B b = new B(new ConditionABC());
C c = new C(new ConditionABC());
Thread ta = new Thread(a);
Thread tb = new Thread(b);
Thread tc = new Thread(c);
ta.start();
tb.start();
tc.start();
}
static class A implements Runnable{
private ConditionABC conditionABC;
public A(ConditionABC conditionABC){
this.conditionABC = conditionABC;
}
@Override
public void run() {
for(int i = 1; i < 20; i++){
conditionABC.printA();
}
}
}
static class B implements Runnable{
private ConditionABC conditionABC;
public B(ConditionABC conditionABC){
this.conditionABC = conditionABC;
}
@Override
public void run() {
for(int i = 1; i < 20; i++){
conditionABC.printB();
}
}
}
static class C implements Runnable{
private ConditionABC conditionABC;
public C(ConditionABC conditionABC){
this.conditionABC = conditionABC;
}
@Override
public void run() {
for(int i = 1; i < 20; i++){
conditionABC.printC();
}
}
}
}
以上就是AQS的整体逻辑,JUC包实现的基石,当然还有很多没有说到的。

浙公网安备 33010602011771号