当一个持锁的线程调用condition的await()方法时,AQS就将该线程封装成一个节点,丢进条件队列里,同时释放锁,从同步队列里移除。当它被唤醒时,又被加到同步队列里去排队抢锁。Condition的await()、signal()、signalAll()方法分别对应着Object的wait()、notify()、notifyAll()方法。二者的区别在于一个AQS锁可以创建多个条件队列,而Object作为锁时只有一个条件队列。
condition的一个常见应用场景是阻塞队列(BlockingQueue),我们可以看个实例:
package lnstark.locks;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* by Lnstark
* 2021/1/9
*/
public class MyBlockingQueue {
private Lock lock = new ReentrantLock();
private Condition putCond = lock.newCondition();
private Condition takeCond = lock.newCondition();
private T[] queue;
private int size = 0, capacity = 0;
private int putIdx = 0, takeIdx = 0;
public int getSize() {
return size;
}
public MyBlockingQueue(int capacity) {
this.capacity = capacity;
queue = (T[]) new Object[capacity];
}
/**
* 生产
*/
public void put(T t) {
lock.lock();
try {
// 如果满了,生产者等待
// 在被唤醒之后到获取锁之前可能会有其他线程先拿到锁,执行了put
// 所以用while而不是If,下同
while (size == capacity) {
System.out.println(Thread.currentThread().getName() + "等待");
putCond.await();
System.out.println(Thread.currentThread().getName() + "唤醒");
}
// 将对象丢进队列,移动生产对象下标
queue[putIdx++] = t;
size++;
// 判断是否到头
if (putIdx == capacity) {
putIdx = 0;
}
System.out.println(Thread.currentThread().getName() + "生产了" + t);
takeCond.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + "释放锁");
}
}
/**
* 消费
*/
public T take() {
lock.lock();
try {
// 如果空了,消费者等待
while (size == 0) {
System.out.println(Thread.currentThread().getName() + "等待");
takeCond.await();
System.out.println(Thread.currentThread().getName() + "唤醒");
}
// 将对象丢进队列,移动生产对象下标
if (queue[takeIdx] == null) {
System.out.println();
}
T t = queue[takeIdx];
queue[takeIdx++] = null;
size--;
// 判断是否到头
if (takeIdx == capacity) {
takeIdx = 0;
}
putCond.signal();
System.out.println(Thread.currentThread().getName() + "消费" + t);
return t;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return null;
}
public static void main(String[] args) {
MyBlockingQueue queue = new MyBlockingQueue<>(10);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 20; i++) {
final int fi = i;
new Thread(() -> {
queue.put(fi);
}, "producer" + i).start();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 20; i++) {
final int fi = i;
new Thread(() -> {
queue.take();
// System.out.println("consumer" + fi + "消费: " + queue.take());
}, "consumer" + i).start();
}
});
// queue.take();
t1.start();
t2.start();
}
}
执行结果(部分截取):
consumer5等待
consumer6等待
consumer13等待
consumer9等待
consumer10等待
consumer12等待
consumer16等待
producer4生产了4
producer4释放锁
consumer17消费4
consumer8等待
consumer11等待
下面看看Condition源码部分。
一、await方法
await方法主要就是,先将当前线程封装成节点加入条件队列,然后释放锁,挂起线程。被唤醒(可能是前驱节点唤醒也可能是中断唤醒)之后就在阻塞队列里去竞争抢锁。
public final void await() throws InterruptedException {
// 如果发生过中断,则抛出异常
if (Thread.interrupted())
throw new InterruptedException();
// 将当前线程封装成condition节点,并加入条件队列
Node node = addConditionWaiter();
// 完全释放锁,并返回锁的重入次数state
int savedState = fullyRelease(node);
// 中断模式:
// 0,表示未发生过中断,
// THROW_IE,即-1,表示在条件队列内发生过中断
// REINTERRUPT,即1,表示在同步队列内发生了中断
int interruptMode = 0;
// 如果不在同步队列内
while (!isOnSyncQueue(node)) {
// 挂起线程
LockSupport.park(this);
// 判断中断状态,如果发生了中断就跳出循环
// checkInterruptWhileWaiting方法里如果在条件队列内发生了中断
// 节点会被移到同步队列里
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 唤醒之后并且已经在同步队列里了,于是就去acquireQueued竞争抢锁
// 如果抢到锁,就把之前的state设置回去
// 如果acquireQueued发生中断就返回true,如果中断状态不是THROW_IE,就设置为REINTERRUPT
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// 如果节点移到阻塞队列之后nextWaiter没断开
// 就执行unlinkCancelledWaiters清除条件队列里的cancelled节点
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
// 如果挂起到唤醒过程里发生过中断,就执行reportInterruptAfterWait处理中断
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
我们具体看看里面的小方法,先来看addConditionWaiter():
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
// 如果尾节点是cancelled状态,则清空队列里的cancelled节点
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;
}
unlinkCancelledWaiters方法:
private void unlinkCancelledWaiters() {
// 遍历并移除队内的cancelled节点
// t是当前节点,trail是当前节点的前置节点
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;
}
}
就是用三个指针遍历并移除cancelled节点的链表操作算法,不难看懂。
下面是释放锁fullyRelease(node)方法:
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,需要注意的是释放锁失败,或者当前未持锁都会抛出异常,然后将当前节点置为cancelled状态。下面看isOnSyncQueue(node),判断节点是否在同步队列里的方法:
final boolean isOnSyncQueue(Node node) {
// 如果waitStatus是CONDITION 那肯定还在条件队列
// node.prev == null说明可能waitStatus已经被修改,但是还没来得及修改node.prev
// 节点正在转移到同步队列。或者他是canclled状态,那就呆在条件队列里等待被清除。
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
// 如果next不为空,那么他已经在同步队列里
// 因为入队先设置node.prev,再CAS node为tail,最后前尾节点的next指向node
if (node.next != null) // If has successor, it must be on queue
return true;
// 在条件队列里面找
return findNodeFromTail(node);
}
findNodeFromTail方法:
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
遍历直到找到node。
下面是checkInterruptWhileWaiting方法:
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
如果没发生中断返回0。
transferAfterCancelledWait方法判断是否在条件队列内中断:
final boolean transferAfterCancelledWait(Node node) {
// 如果cas成功,说明状态还是CONDITION。
// 如果已经转移至同步队列waitStatus会变成0
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
// 发生中断的节点会直接被转移到同步队列
enq(node);
return true;
}
// 如果不在条件队列里说明正在转移,稍微等一会直到转移完成
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
这两个方法判断了中断情况,如果发生中断了,节点还在条件队列里的话,那就转移至同步队列。
回到awai方法,还有最后一个处理中断结果方法reportInterruptAfterWait:
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
// 如果条件队列内中断就抛出异常
if (interruptMode == THROW_IE)
throw new InterruptedException();
// 否则返回中断状态
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
二、signal方法
signal方法唤醒条件队列里的第一个不是cancelled的节点,将它转移到同步队列。
public final void signal() {
// 如果未持锁,抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
// 如果nextWaiter是null,那队列就空了,将lastWaiter置为null
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
// 断开first节点
first.nextWaiter = null;
// 如果转移失败,那就转移下一个节点
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
transferForSignal方法:
final boolean transferForSignal(Node node) {
// 如果失败,可能是canclled状态的节点,那么不需要转移
// 也可能是被中断的节点,主动修改了状态,正要进入同步队列
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// 入队,返回前置节点
Node p = enq(node);
int ws = p.waitStatus;
// 如果前置节点为cancelled状态(ws > 0),或者CAS失败(可能其他线程正在修改ws)
// 那么就唤醒当前线程,去清除或修改前置节点为signal状态
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
// 入队成功,返回true
return true;
}
这个看懂了signalAll自然也就看懂了,它是遍历并转移队列里每个符合条件的节点。
以上就是条件队列的await和signal的大致逻辑了。