参考: https://blog.csdn.net/javazejian/article/details/75043422
1.Condition (AQS独占锁)
Condition 是一个多线程协调通信的工具类,可以让某些线程一起等待某个条件(condition),只有满足条件时,线程才会被唤醒.
condition 中两个最重要的方法,一个是 await,一个是 signal 方法。
await:把当前线程阻塞挂起;
signal:唤醒阻塞的线程;
调用 Condition,需要获得 Lock 锁,所以意味着会存在一个 AQS 同步队列。
查看源码类图关系可以知道,Condition接口的实现类是 ConditionObject,它是 AbstractQueuedSynchronizer 中的一个内部类
2.condition .await
调用Condition的await()方法(或者以await开头的方法),会使当前线程进入等待队列并释放锁,同时线程状态变为等待状态。
当从 await()方法返回时,当前线程一定获取了 Condition 相关联的锁。
public final void await() throws InterruptedException {
if (Thread.interrupted()) //表示 await 允许被中断
throw new InterruptedException();
// 1. 将当前线程包装成Node,尾插入到等待队列中
Node node = addConditionWaiter(); //创建一个新的节点,节点状态为 condition,采用的数据结构仍然是链表
// 2. 释放当前线程所占用的lock,在释放的过程中会唤醒同步队列中的下一个节点
int savedState = fullyRelease(node); //释放当前的锁,得到锁的状态,并唤醒 AQS 队列中的一个线程
int interruptMode = 0; //如果当前节点没有在同步队列上,即还没有被 signal,则将当前线程阻塞
while (!isOnSyncQueue(node)) { //判断这个节点是否在 AQS 队列上,第一次判断的是 false,因为前面已经释放锁了
// 3. 当前线程进入到等待状态
LockSupport.park(this);//通过 park 挂起当前线程
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 当这个线程醒来,会尝试拿锁, 当 acquireQueued 返回 false 就是拿到锁了
// interruptMode != THROW_IE -> 表示这个线程没有成功将 node 入队,但 signal 执行了 enq 方法让其入队了
// 4. 自旋等待获取到同步状态(即获取到lock)
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT; // 将这个变量设置成 REINTERRUPT
// 如果 node 的下一个等待者不是 null, 则进行清理,清理 Condition 队列上的节点;否则,无需清理
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
// 如果线程被中断了,需要抛出异常;否则不需要处理
// 5. 处理被中断的情况
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
当前线程调用condition.await()方法后,会使得当前线程释放lock然后加入到等待队列中,直至被signal/signalAll后会使得当前线程从等待队列中移至到同步队列中去,直到获得了lock后才会从await方法返回,或者在等待时被中断会做中断处理。
3.addConditionWaiter
这个方法的主要作用是把当前线程封装成 Node,添加到等待队列.
这里的队列不再是双向链表,而是单向链表.
/**
* Adds a new waiter to wait queue.
* @return its new wait node
*/
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
// 如果 lastWaiter 不等于空并且waitStatus 不等于 CONDITION 时,把这个节点从链表中移除
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
// 构建一个Node,waitStatus=CONDITION
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
4.fullyRelease
彻底的释放锁,什么叫彻底呢?就是如果当前锁存在多次重入,那么在这个方法中只需要释放一次,就会把所有的重入次数归零。
/**
* Invokes release with current state value; returns saved state.
* Cancels node and throws exception on failure.
* @param node the condition node for this wait
* @return previous sync state
*/
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;
}
}
5.isOnSyncQueue
判断当前节点是否在同步队列中,返回 false 表示不在,返回 true 表示在.
如果不在 AQS 同步队列,说明当前节点没有唤醒去争抢同步锁,所以需要把当前线程阻塞起来,直到其他的线程调用 signal 唤醒;
如果在 AQS 同步队列,意味着它需要去竞争同步锁去获得执行程序执行权限;
为什么要做这个判断呢?原因是在 condition 队列中的节点会重新加入到 AQS 队列去竞争锁。也就是当调用 signal的时候,会把当前节点从 condition 队列转移到 AQS 队列;
/**
* Returns true if a node, always one that was initially placed on
* a condition queue, is now waiting to reacquire on sync queue.
* @param node the node
* @return true if is reacquiring
*/
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;
/*
* node.prev can be non-null, but not yet on queue because
* the CAS to place it on queue can fail. So we have to
* traverse from tail to make sure it actually made it. It
* will always be near the tail in calls to this method, and
* unless the CAS failed (which is unlikely), it will be
* there, so we hardly ever traverse much.
*/
return findNodeFromTail(node);
}
思考:如何判断某个线程 ThreadA 被封装的节点是否在AQS队列中?
(1).如果 ThreadA 的 waitStatus 的状态为 CONDITION,说明它存在于 condition 队列中,不在 AQS 队列。因为AQS 队列的状态一定不可能有 CONDITION ;
(2).如果 node.prev 为空,说明也不存在于 AQS 队列,原因是 prev=null 在 AQS 队列中只有一种可能性,就是它是head 节点,head 节点意味着它是获得锁的节点。
(3).如果 node.next 不等于空,说明一定存在于 AQS 队列中,因为只有 AQS 队列才会存在 next 和 prev 的关系;
(4).findNodeFromTail,表示从 tail 节点往前扫描 AQS 队列,一旦发现 AQS 队列的节点和当前节点相等,说明节点一定存在于 AQS 队列中
6.Condition.signal
await 方法会阻塞 ThreadA,然后 ThreadB 抢占到了锁获得了执行权限,这个时候在 ThreadB 中调用了 Condition 的 signal()方法,将会唤醒在等待队列中节点.
public final void signal() {
if (!isHeldExclusively()) // 先判断当前线程是否获得了锁,这个判断比较简单,直接用获得锁的线程和当前线程相比即可
throw new IllegalMonitorStateException();
Node first = firstWaiter; // 拿到 Condition 队列上第一个节点
if (first != null)
doSignal(first);
}
7.Condition.doSignal
对 condition 队列中从首部开始的第一个 condition 状态的节点,执行 transferForSignal 操作,将 node 从 condition 队列中转换到 AQS 队列中,同时修改 AQS 队列中原先尾节点的状态.
private void doSignal(Node first) {
do {
// 从 Condition 队列中删除 first 节点
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null; // 将 next 节点设置成 null
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
8.AQS.transferForSignal
该方法先是 CAS 修改了节点状态,如果成功,就将这个节点放到 AQS 队列中,然后唤醒这个节点上的线程。
此时,那个节点就会在 await 方法中苏醒
/**
* Transfers a node from a condition queue onto sync queue.
* Returns true if successful.
* @param node the node
* @return true if successfully transferred (else the node was
* cancelled before signal)
*/
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
Node p = enq(node); // 调用 enq,把当前节点添加到 AQS 队列。并且返回返回按当前节点的上一个节点,也就是原 tail 节点
int ws = p.waitStatus;
// 如果上一个节点的状态被取消了, 或者尝试设置上一个节点的状态为 SIGNAL 失败了(SIGNAL 表示: 他的 next 节点需要停止阻塞)
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread); // 唤醒节点上的线程
return true; //如果 node 的 prev 节点已经是 signal 状态,那么被阻塞的 ThreadA 的唤醒工作由 AQS 队列来完成
}
9.被阻塞的线程唤醒后的逻辑
前面在分析 await 方法时,线程会被阻塞。而通过 signal 被唤醒之后又继续回到上次执行的逻辑中如下部分的代码
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
checkInterruptWhileWaiting:
如果当前线程被中断,则调用 transferAfterCancelledWait 方法判断后续的处理应该是抛出 InterruptedException 还是重新中断。
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
这里需要注意的地方是,如果第一次 CAS 失败了,则不能判断当前线程是先进行了中断还是先进行了 signal 方法的调用,可能是先执行了 signal 然后中断,也可能是先执行了中断,后执行了 signal,当然,这两个操作肯定是发生在 CAS 之前。
这时需要做的就是等待当前线程的 node 被添加到 AQS 队列后,也就是 enq 方法返回后,返回 false 告诉 checkInterruptWhileWaiting 方法返回 REINTERRUPT(1),后续进行重新中断。
简单来说,该方法的返回值代表当前线程是否在 park 的时候被中断唤醒:
(1).如果为 true 表示中断在 signal 调用之前,signal 还未执行,那么这个时候会根据 await 的语义,在 await 时遇到中断需要抛出 interruptedException,返回 true 就是告诉 checkInterruptWhileWaiting 返回 THROW_IE(-1)。
(2).如果返回 false,否则表示 signal 已经执行过了,只需要重新响应中断即可。
10.transferAfterCancelledWait
使用 cas 修改节点状态,如果还能修改成功,说明线程被中断时,signal 还没有被调用。
这里有一个知识点,就是线程被唤醒,并不一定是在 java 层面执行了 locksupport.unpark,也可能是调用了线程的 interrupt()方法,这个方法会更新一个中断标识,并且会唤醒处于阻塞状态下的线程。
/**
* Transfers node, if necessary, to sync queue after a cancelled wait.
* Returns true if thread was cancelled before being signalled.
*
* @param node the node
* @return true if cancelled before the node was signalled
*/
final boolean transferAfterCancelledWait(Node node) {
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
enq(node); //如果 cas 成功,则把 node 添加到 AQS 队列
return true;
}
/*
* If we lost out to a signal(), then we can't proceed
* until it finishes its enq(). Cancelling during an
* incomplete transfer is both rare and transient, so just
* spin.
*/
while (!isOnSyncQueue(node)) // 循环检测 node 是否已经成功添加到 AQS 队列中
Thread.yield(); // 如果没有,则通过 yield
return false;
}
11.reportInterruptAfterWait
根据 checkInterruptWhileWaiting 方法返回的中断标识来进行中断上报:
如果是 THROW_IE,则抛出中断异常;
如果是 REINTERRUPT,则重新响应中断;
/**
* Throws InterruptedException, reinterrupts current thread, or
* does nothing, depending on mode.
*/
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
12.Condition 总结
参考: https://www.jianshu.com/p/28387056eeb4
condition 是要和 lock 配合使用的也就是 Condition 和 Lock 是绑定在一起的,而 Lock 的实现原理又依赖于AQS;
锁机制的实现上,AQS内部维护了一个同步队列,如果是独占式锁的话,所有获取锁失败的线程的尾插入到同步队列;
同样的,condition内部也是使用同样的方式,内部维护了一个 等待队列,所调用condition.await方法的线程会加入到等待队列中,并且线程状态转换为等待状态。
(1).await方法被调用:同步队列的头节点尾插入到等待队列;
(2).signal方法被调用:等待队列的头节点尾插入到同步队列;
(3).通过使用condition提供的await和signal/signalAll方法就可以实现 “等待/通知机制”而这种机制能够解决最经典的问题就是“生产者与消费者问题。
await和signal和signalAll方法就像一个开关控制着线程A(等待方)和线程B(通知方)。它们之间的关系可以用下面一个图来表现得更加贴切:
线程awaitThread先通过lock.lock()方法获取锁成功后调用了condition.await方法进入等待队列,
而另一个线程signalThread通过lock.lock()方法获取锁成功后调用了condition.signal或者signalAll方法,
使得线程awaitThread能够有机会移入到同步队列中,当其他线程释放lock后使得线程awaitThread能够有机会获取lock,
从而使得线程awaitThread能够从await方法中退出,执行后续操作。如果awaitThread获取lock失败会直接进入到同步队列。