在前一章节中,我们简单分析过aqs中加锁以及阻塞的流程,这一章我们来分析一下condition条件阻塞工具的实现
## 什么是condition
condition是作为条件阻塞器,通过调用await,signal和signalAll方法来阻塞和唤醒线程,可以横向对比的是Object对象的wait,notify以及notifyAll方法,值得注意的是,与Object的wait需要跟synchronized结合使用一样,condition也需要跟锁结合使用,比如ReenTrantLock中的newCondition方法就是创建一个全新的条件阻塞器,而调用await方法也需要通过lock进行加锁才可以正常使用.
## condition.await与Object.wait的区别
* 首先Object相关的阻塞方法都是通过本地方法实现的,而condition的阻塞和唤醒方法都是通过java调用来实现的,其次就是每个Object只能绑定一个阻塞器,即synchronized所绑定的对象,只有通过调用该对象的wait和notify方法才能实现阻塞以及唤醒,并且notify会在调用wait方法的线程中随机挑选一个唤醒
* 而一个lock可以创建多个condition,例如ReentrantLock中的newCondition方法每次调用都会返回一个新的条件阻塞器,这样做的好处是,调用condition方法的signal只会唤醒当前condition调用await方法阻塞的线程,利用这种模式可以实现阻塞队列,如经典的ArrayBlockingQueue就是利用ReenTrantLock创建了两个condition控制队列空时的阻塞以及队列满时的阻塞
## await方法的实现
老规矩先上源码
```java
public final void await() throws InterruptedException {
//检测线程是否中断
if (Thread.interrupted())
throw new InterruptedException();
//添加一个condition的waiter
Node node = addConditionWaiter();
//释放锁
int savedState = fullyRelease(node);
//打断模式
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
//进行循环如果当前线程不在同步队列中,则阻塞线程
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);
}
```
await的实现并不难理解,而操作的Node节点与AQS中的node节点是一个对象,通过标记node的waitStatus变量来判断当前node的状态,我们再来看看addConditionWaiter的实现
```java
private Node addConditionWaiter() {
Node t = lastWaiter;
//如果尾部的等待node被取消了,则遍历取消所有的被取消的节点
if (t != null && t.waitStatus != Node.CONDITION) {
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;
}
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
Node trail = null;
//遍历取消所有节点状态不是condition的节点
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;
}
}
```
在这个方法中,值得注意的是通过condition维护的队列,与aqs中排队的队列是两个完全不同的队列,condition的队列维护在condition对象中,通过firstWaiter和lastWaiter变量来维护队列的头与尾,我们继续往下看fullyRelease方法
```java
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;
}
}
```
fullyRelease方法可见是直接释放当前独占锁,在java中目前只有ReenTrantLock以及ReentrantReadWriteLock,实现了newCondition方法,所以共享锁是不允许condition阻塞的,
继续向下看isOnSyncQueue方法,顾名思义该方法是判断当前node是否在同步队列总
```java
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null) // If has successor, it must be on queue
return true;
return findNodeFromTail(node);
}
```
首先是校验当前节点的状态,如果节点状态还是condition那么一定没有插入队列中,而同样node.prev前面节点如果为空自然是也没有插入队列的,后续判断node.next同样是判断后续有没有等待节点,这里值得注意的是,node.next是同步队列节点的下一个节点,而condition阻塞队列的节点为nextWaiter不要弄混了,
如果这两步判断没有成功的话,说明当前节点的prev节点不为空,而next节点为空,而node.prev节点不为空,,但是还没有在队列上,因为有可能cas失败,所以要从尾部遍历一遍确定在没在节点中.
如果在同步队列中则调用acquireQueued尝试获取锁或者排队,接下来就是判断是否打断等流程,后续不在赘述,接下来我们康康signal方法
```java
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);
}
final boolean transferForSignal(Node node) {
//如果换失败,则只有可能是被取消了
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//将当前线程插入队列,并返回node节点前面的节点,
Node p = enq(node);
int ws = p.waitStatus;
//修改前一个节点的状态为signle以便被唤醒
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
//如果线程被取消了,或者将waitstatus修改失败的话,说明当前线程已经被取消了
LockSupport.unpark(node.thread);
return true;
}
```
首先调用signal时一定是要以独占锁的模式调用,否则会抛出异常,然后将当前等待节点后移,并且将当前的节点插入阻塞队列中,不需要唤醒线程因为调用signal时一定是已经被某一线程获取了锁,而当调用release时会释放锁并且自动调用后续的锁
那么这里有一个问题就是为什么会在这里调用一次unpark,就算不调用,等到下次唤醒的时候,也会清除掉被取消的节点,这里我查阅资料发现,这次唤醒主要是提升性能,在这里唤醒一次,将前面取消的节点都删除,以便下次唤醒不需要在删除节点.这里加不加这个唤醒逻辑上是一样的
我们再来看看signalAll方法
```java
public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
}
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}
```
这里实现与signal几乎相同,只不过一个是将first节点插入队列,而signalAll方法则是将后续队列全部插入同步队列中
到这里我们就已经将condition的实现完全理清了,后续我们也会再分析利用condition来实现的同步阻塞队列ArrayBlockingQueue