同步队列和条件队列是AQS中的两种不同队列,同步队列主要用于实现锁机制,而条件队列用于线程间的协调和通信。(本文代码为经典的JDK 1.8)
同步队列主要用于实现锁的获取和释放。如我们常用的ReentrantLock,就是基于同步队列来实现的。
我们在介绍AQS的时候介绍过,它是一个FIFO队列,节点类型为AQS内部的Node类。当一个线程尝试获取锁失败时,它会被封装成一个Node节点加入到队列的尾部(每个节点(Node)代表一个等待的线程)。当锁被释放时,头节点的线程会被唤醒,尝试再次获取锁。
static final class Node {
// 前驱和后继节点,构成双向链表
Node prev;
Node next;
// 线程本身
Thread thread;
// 状态信息,表示节点在同步队列中的等待状态
int waitStatus;
// ...
}
同步的队列的实现原理比较简单:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// 尝试快速路径:直接尝试在尾部插入节点
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 快速路径失败时,进入完整的入队操作
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // 队列为空,初始化
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
条件队列用于实现条件变量,允许线程在特定条件不满足时挂起,直到其他线程改变了条件并显式唤醒等待在该条件上的线程。比较典型的一个条件队列的使用场景就是ReentrantLock的Condition。
条件队列与同步队列不同,它是基于Condition接口实现的,用于管理那些因为某些条件未满足而等待的线程。当条件满足时,这些线程可以被唤醒。每个Condition对象都有自己的一个条件队列。
ConditionObject是AQS的一个内部类,用于实现条件变量。条件变量是并发编程中一种用于线程间通信的机制,它允许一个或多个线程在特定条件成立之前等待,同时释放相关的锁。这在某种程度上类似于对象监视器模式中的wait()和notify()方法,但提供了更灵活和更强大的控制。
public class ConditionObject implements Condition, java.io.Serializable {
// 条件队列的首尾节点
private transient Node firstWaiter;
private transient Node lastWaiter;
// ...
}
他的主要原理如下:
await():使当前线程释放锁并进入等待队列,直到被另一个线程的signal()或signalAll()方法唤醒,或被中断。
public final void await() throws InterruptedException {
// 如果当前线程在进入此方法之前已经被中断了,则直接抛出InterruptedException异常。
if (Thread.interrupted())
throw new InterruptedException();
// 将当前线程加入到等待队列中。
Node node = addConditionWaiter();
// 释放当前线程所持有的锁,并返回释放前的状态,以便以后可以重新获取到相同数量的锁。
int savedState = fullyRelease(node);
// 中断模式,用于记录线程在等待过程中是否被中断。
int interruptMode = 0;
// 如果当前节点不在同步队列中,则表示线程应该继续等待。
while (!isOnSyncQueue(node)) {
// 阻塞当前线程,直到被唤醒或中断。
LockSupport.park(this);
// 检查线程在等待过程中是否被中断,并更新interruptMode状态。
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 当节点成功加入到同步队列后,尝试以中断模式获取锁。
// 如果在此过程中线程被中断,且不是在signal之后,则设置中断模式为REINTERRUPT。
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// 如果节点后面还有等待的节点,从等待队列中清理掉被取消的节点。
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
// 根据中断模式处理中断。
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
signal():唤醒等待队列中的头节点对应的线程。
signalAll():唤醒所有等待的线程。
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
条件队列和同步队列,主要有以下区别: