Condition 经常可以用在生产者-消费者的场景中
Condition提供了signal和await方法是用来配合锁 实现线程 间同 步 的基础设施。都必须获取到锁才能进行操作。Object的notify 和 wait,是配 合 synchronized 内置锁实现线程间同步 的基础设施一样。不同在于, synchronized同时只能与一个共享变量的notify或wait方法实现同步, 而 AQS 的一个锁可以对应多个条件变量。
public class ProductCoustomerDemo {
private int num=0;
private Lock lock=new ReentrantLock();//获取锁
private Condition condition =lock.newCondition();//获取Condition例子,在使用 condition 时,必须先持有相应的锁
public void doPro(){
lock.lock();
try {
while (num!=10){
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName()+"生产了"+num);
condition.signalAll();
}catch (Exception e){
}finally {
lock.unlock();
}
}
public void doCustomer(){
lock.lock();
try {
while (num==10){
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName()+"消费了"+num);
condition.signalAll();
}catch (Exception e){
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
ProductCoustomerDemo productCoustomerDemo=new ProductCoustomerDemo();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
productCoustomerDemo.doPro();
}
},"AA").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
productCoustomerDemo.doCustomer();
}
},"BB").start();
}
lock.newCondition()的作用 其实是 new 了 一 个在 AQS 内部 声 明的 ConditionObject 对 象 , ConditionObject 是 AQS 的内部 类 ,可以访问 AQS 内部的变 量(例 如状态变量 state)和方法 。在每个条件变量 内部都维护了 一个条件队列,用来存放调用条 件变量的 await()方法时被阻塞的线程。
1.每个 ReentrantLock 实例可以对应多个条件变量。调用多次 newCondition 产生多个 ConditionObject 的实例。
final ConditionObject newCondition() {
return new ConditionObject();
}
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;
AQS 的时候,我们有一个阻塞队列,用于保存等待获取锁的线程的队列,叫条件队列,一个锁对应一个 AQS 阻塞队列,对应多个条 件变量, 每个条件变量有自己的一个条件队列。
[外链图片转存失败(img-VKfBtJdn-1569164110083)(/Users/laiyanxin/Library/Application Support/typora-user-images/image-20190922220514658.png)]
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//创建新的node节点,并插入到条件队列末尾
Node node = addConditionWaiter();
//释放当前线程获取锁
int savedState = fullyRelease(node);
int interruptMode = 0;
//判断 当前节点是否在同步队列,如果不在就挂起
while (!isOnSyncQueue(node)) {
//调用park方法阻塞拉起当前线程
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);
}
// 将当前线程对应的节点入队,插入队尾
private Node addConditionWaiter() {
Node t = lastWaiter;
// 如果 最后一个节点是取消了 ,将其清除出去
if (t != null && t.waitStatus != Node.CONDITION) {
//遍历整个条件 队列,然后 将已取消的节点 清除出去 队列
unlinkCancelledWaiters();
t = lastWaiter;
}
//新建 一个节点,指定 waitStatus 为 Node.CONDITION
Node node = new Node(Thread.currentThread(), Node.CONDITION);、
// t 此时是 lastWaiter,队尾
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
用于清除队列中已经取消等待的节点。
// 清除队列中已经取消等待的节点
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;
}
}
是完全释放独占锁,返回释放锁之前state的值,如果释放锁之前,state=2,说明是可重入锁,则返回savedState是 2,当它被唤醒的时候,它需要重新持有 2 把锁,才能继续下去。
//是完全释放独占锁,返回释放锁之前state的值,如果释放锁之前,state=2,说明是可重入锁,则返回savedState是 2
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();
// 这里使用了当前的 state 作为 release 的参数,也就是完全释放掉锁,将 state 置为 0
if (release(savedState)) {
failed = false;
return savedState;
} else {
// 如果不持有锁,会将节点设置为"取消"状态,并抛出异常 IllegalMonitorStateException
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
用于判断节点是否已经转移到阻塞队列了
// 移动过去的时候,node 的 waitStatus 会置为 0,这个之后在说 signal 方法的时候会说到
// 如果 waitStatus 还是 Node.CONDITION,也就是 -2,那肯定就是还在条件队列中
// 如果 node 的前驱 prev 指向还是 null,说明肯定没有在 阻塞队列(prev是阻塞队列链表中使用的)
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
// 如果 node 已经有后继节点 next 的时候,那肯定是在阻塞队列了
if (node.next != null) // If has successor, it must be on queue
return true;
// 这个方法从阻塞队列的队尾开始从后往前遍历找,如果找到相等的,说明在阻塞队列,否则就是不在阻塞队列
return findNodeFromTail(node);
}
isOnSyncQueue(node) 返回 false 的话,那么进到 LockSupport.park(this);
这里线程挂起。
这个 方法是 唤醒等待了最久的线程,将这个线程对应的 node 从条件队列转移到阻塞队列
public final void signal() {
//判断 获取锁的线程是不是当前线程
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
//判断第一个节点的下个节点是不是null,是null说明后面没有节点在等待了
//将 lastWaiter 置为 null
//设置当前first的下个节点也为null,脱离节点
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
// 这里 while 循环,如果 first 转移不成功,那么选择 first 后面的第一个节点进行转移,依此类推
}
final boolean transferForSignal(Node node) {
// CAS 如果失败,说明此 node 的 waitStatus 已不是 Node.CONDITION,说明节点已经取消,
// 既然已经取消,也就不需要转移了,方法返回,转移后面一个节点
// 否则,将 waitStatus 置为 0
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// enq(node): 自旋进入阻塞队列的队尾
// 注意,这里的返回值 p 是 node 在阻塞队列的前驱节点
Node p = enq(node);
int ws = p.waitStatus;
// ws > 0 说明 node 在阻塞队列中的前驱节点取消了等待锁,直接唤醒 node 对应的线程。唤醒之后会怎么样,后面再解释
// 如果 ws <= 0, 那么 compareAndSetWaitStatus 将会被调用,上篇介绍的时候说过,节点入队后,需要把前驱节点的状态设为 Node.SIGNAL(-1)
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
// 如果前驱节点取消或者 CAS 失败,会进到这里唤醒线程,之后的操作看下一节
LockSupport.unpark(node.thread);
return true;
}
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
//线程被唤醒后 ,继续执行以下代码
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 1. 如果在 signal 之前已经中断,返回 THROW_IE
// 2. 如果是 signal 之后中断,返回 REINTERRUPT
// 3. 没有发生中断,返回 0
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
先解释下 interruptMode。interruptMode 可以取值为 REINTERRUPT(1),THROW_IE(-1),0
Thread.interrupted():如果当前线程已经处于中断状态,那么该方法返回 true,同时将中断状态重置为 false,所以,才有后续的 重新中断(REINTERRUPT)
的使用。
看看怎么判断是 signal 之前还是之后发生的中断:
final boolean transferForSignal(Node node) {
// 只有线程处于中断状态,才会调用此方法
// 如果需要的话,将这个已经取消等待的节点转移到阻塞队列
// 返回 true:如果此线程在 signal 之前被取消,
final boolean transferAfterCancelledWait(Node node) {
// 用 CAS 将节点状态设置为 0
// 如果这步 CAS 成功,说明是 signal 方法之前发生的中断,因为如果 signal 先发生的话,signal 中会将 waitStatus 设置为 0
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
// 将节点放入阻塞队列
// 这里我们看到,即使中断了,依然会转移到阻塞队列
enq(node);
return true;
}
// 到这里是因为 CAS 失败,肯定是因为 signal 方法已经将 waitStatus 设置为了 0
// signal 方法会将节点转移到阻塞队列,但是可能还没完成,这边自旋等待其完成
// 当然,这种事情还是比较少的吧:signal 调用之后,没完成转移之前,发生了中断
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode)
这里的 acquireQueued(node, savedState) 的第一个参数 node 之前已经经过 enq(node) 进入了队列,参数 savedState 是之前释放锁前的 state,这个方法返回的时候,代表当前线程获取了锁,而且 state == savedState了。
本文参考https://www.javadoop.com/post/AbstractQueuedSynchronizer-2
书籍《java并发编程之美》