Condition条件队列是为了实现线程之间相互等待的问题。注意Condition对象只能在独占锁中才能使用。
举个例子:有两个线程,生产者线程,消费者线程。
当消费者线程消费产品时,发现没有产品,这时它就要等待,让生产者线程生产产品出来后,再通知它消费。同样的,当生产者线程生产产品时,发现存储产品的仓库(列表)容量已经满了,这时它就要等待,让消费者消费产品后,再通知它生产产品。
因为操作的是同一个资源,所以要加锁,防止多线程冲突。而锁在同一时间只能有一个线程持有,所以消费者/生产者在让线程等待前,必须释放锁,且唤醒另一个等待锁的线程。那么在AQS中Condition条件又是如何实现的呢?
- 首先在AQS内部中存在一个同步等待队列,和一个Condition条件等待队列,存储着所有因Condition条件陷入等待的线程
- await系列方法:让当前持有锁的线程释放锁,并唤醒一个在同步等待队列上等待锁的线程,再为当前线程创建一个node节点,插入到Condition条件队列中
- signal系列方法:其实这里没有唤醒任何线程,而是将Condition条件队列上的等待节点插入到同步等待队列中,所以当持有锁的线程执行完毕释放锁时,就会唤醒同步等待队列中的一个线程,这个时候才会唤醒线程
await()方法源码详解
public final void await() throws InterruptedException {
if (Thread.interrupted()) // 线程处于中断状态,则抛出异常
throw new InterruptedException();
Node node = addConditionWaiter(); // 添加新的等待结点node到条件队列末尾
int savedState = fullyRelease(node); // 释放当前占有的锁
int interruptMode = 0;
while (!isOnSyncQueue(node)) { // 判断node结点是否在同步队列中(一直循环到有人调用signal系列方法,将node结点从条件队列中插入到同步队列中,才终止循环)
LockSupport.park(this); // node结点不在同步队列中(即没有人调用signal系列方法唤醒),让当前线程陷入等待
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) // 在条件队列等待期间,如果有人主动中断等待,则跳出循环
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE) // node结点已在同步队列中,并且去唤醒node节点中的线程
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // 清空已被取消的结点
unlinkCancelledWaiters();
if (interruptMode != 0) // interruptMode不等于0,抛出异常或者中断请求
reportInterruptAfterWait(interruptMode);
}
- addConditionWaiter()方法添加新的等待结点到条件队列末尾:
private Node addConditionWaiter() {
Node t = lastWaiter; // lastWaiter为条件队列中最后一个等待结点
if (t != null && t.waitStatus != Node.CONDITION) { // 最后等待结点的waitStatus不等于CONDITION,说明该结点已被取消,此时等于CANCELLED。 (在条件队列中,等待中结点的waitStatus等于CONDITION;已被取消结点的waitStatus等于CANCELLED)
unlinkCancelledWaiters(); // 清空条件队列中的已被取消的结点
t = lastWaiter;
}
// 根据当前线程和CONDITION状态创建新的节点node,并添加到条件队列末尾
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return 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;
}
}
public final boolean release(int arg) {
if (tryRelease(arg)) { // 尝试释放占用的锁
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // 唤醒同步队列中头结点中的线程
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases; // 当前锁被占用次数,减去需要释放占用锁次数
if (Thread.currentThread() != getExclusiveOwnerThread()) // 当前线程不是占用锁的线程,则抛出异常
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) { // 等于0,说明锁可以释放
free = true;
setExclusiveOwnerThread(null); // 设置占用锁线程为空
}
setState(c);
return free;
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0) // waitStatus小于0,就将状态重新设置为0,表示这个node节点已经完成了
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next; // 获取node的后继结点
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); // 唤醒后继结点中的线程
}
- isOnSyncQueue()方法判断节点是否存在同步队列中:
final boolean isOnSyncQueue(Node node) {
/* 同步队列中的结点和条件队列中的不同:
1. 同步队列中的结点是双向,拥有next指向后继结点,prev指向前任结点
2. 条件队列中的结点时单向,只有nextWaiter指向后继结点 */
if (node.waitStatus == Node.CONDITION || node.prev == null) // node结点waitStatus等于CONDITION,或者前任结点为空,则说明在条件队列中
return false;
if (node.next != null) // node结点next属性不为空,则说明在同步队列中
return true;
return findNodeFromTail(node); // 不满足前面的条件,说明node结点可能已插入同步队列中。因此再从同步队列中从后往前搜索
}
private boolean findNodeFromTail(Node node) {
// 从后往前搜索node节点
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
- acquireQueued()方法不断尝试获取锁:
该方法属于AQS中同步队列中获取锁的方式,因为本文讨论的是Condition条件队列,所以在此不做讨论。如果想要了解同步队列如何获取和释放锁的朋友可以看我另外两篇博文:
AbstractQueuedSynchronizer(AQS)源码分析——独占锁
AbstractQueuedSynchronizer(AQS)源码分析——共享锁
signal()方法源码详解
public final void signal() {
if (!isHeldExclusively()) // 占用锁的线程不是当前线程,则抛出异常(signal方法必须是在占用锁的线程当中调用,和notify方法只能在synchronize代码块中执行同样的道理)
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first); // 将条件队列的第一个节点插入到同步队列中
}
- isHeldExclusively()方法判断占用锁的线程是否是当前线程:
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
- doSignal()方法删除并转移结点:
private void doSignal(Node first) {
do { // 循环查找不为空的头结点,将其从条件队列中删除,并插入到同步队列中
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) && // 将first结点插入到同步队列中
(first = firstWaiter) != null);
}
- transferForSignal()方法将条件队列结点插入到同步队列中:
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) // 转换node结点的waitStatus属性
return false; // 更新失败,说明该结点已被取消,返回false
Node p = enq(node); // 插入同步队列末尾
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) // 插入队列前的末尾结点p已被取消,或者将其的waitStatus设置为SIGNAL失败,则唤醒node结点中的线程
LockSupport.unpark(node.thread);
return true;
}
private Node enq(final Node node) {
for (;;) { // 自旋
Node t = tail;
if (t == null) { // 末尾节点为空,则初始化一个空的头结点
if (compareAndSetHead(new Node()))
tail = head;
} else { // 末尾节点不为空,则将node插入到队列末尾
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t; // 插入成功,返回插入队列前的末尾节点
}
}
}
}
其他await系列和signal系列的方法,都与以上源码讲解分析大同小异。如果能明白这篇博文,相信其他方法也难不倒你了。