Aqs的同步队列和条件队列原理?
Aqs的同步队列和条件队列原理?
AQS的同步队列和条件队列原理?
典型回答
同步队列和条件队列是AQS中的两种不同队列,同步队列主要用于实现锁机制,而条件队列用于线程间的协调和通信。(本文代码为经典的JDK 1.8)
同步队列
同步队列主要用于实现锁的获取和释放。如我们常用的ReentrantLock,就是基于同步队列来实现的。
我们在介绍AQS的时候介绍过,它是一个FIFO队列,节点类型为AQS内部的Node类。当一个线程尝试获取锁失败时,它会被封装成一个Node节点加入到队列的尾部(每个节点(Node)代表一个等待的线程)。当锁被释放时,头节点的线程会被唤醒,尝试再次获取锁。
1
2
3
4
5
6
7
8
9
10
static final class Node {
// 前驱和后继节点,构成双向链表
Node prev;
Node next;
// 线程本身
Thread thread;
// 状态信息,表示节点在同步队列中的等待状态
int waitStatus;
// ...
}
同步的队列的实现原理比较简单:
- 当一个线程尝试获取锁并失败时,AQS会将该线程包装成一个节点(Node)并加入到队列的尾部。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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()方法,但提供了更灵活和更强大的控制。
1
2
3
4
5
6
7
public class ConditionObject implements Condition, java.io.Serializable {
// 条件队列的首尾节点
private transient Node firstWaiter;
private transient Node lastWaiter;
// ...
}
他的主要原理如下:
- 当线程调用了Condition的await()方法后,它会释放当前持有的锁,并且该线程会被加入到条件队列中等待。
await():使当前线程释放锁并进入等待队列,直到被另一个线程的signal()或signalAll()方法唤醒,或被中断。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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()方法来通知条件可能已经满足。
signal():唤醒等待队列中的头节点对应的线程。
signalAll():唤醒所有等待的线程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
}
扩展知识
二者区别
条件队列和同步队列,主要有以下区别:
- 目的不同:同步队列主要用于管理锁的获取和释放,而条件队列用于等待特定条件的满足。
- 使用方式不同:同步队列是AQS自动管理的,开发者通常不需要直接与之交互;而条件队列是通过Condition接口暴露给开发者的,需要显式地调用等待(await)和通知(signal/signalAll)方法。
- 队列类型不同:虽然它们都是队列结构,但同步队列是所有基于AQS同步器共享的,每个同步器实例只有一个同步队列;条件队列是每个Condition实例特有的,一个同步器可以有多个Condition对象,因此也就有多个条件队列。
This post is licensed under
CC BY 4.0
by the author.
