1. Condition简介
任何一个java对象都天然继承于Object类,在线程间实现通信的往往会应用到Object的几个方法,比如wait(),wait(long timeout),wait(long timeout, int nanos)与notify(),notifyAll()几个方法实现等待/通知机制,同样的, 在java Lock体系下依然会有同样的方法实现等待/通知机制。从整体上来看Object的wait和notify/notify是与对象监视器配合完成线程间的等待/通知机制,而Condition与Lock配合完成等待通知机制,前者是java底层级别的,后者是语言级别的,具有更高的可控制性和扩展性。两者除了在使用方式上不同外,在功能特性上还是有很多的不同:
- Condition能够支持不响应中断,而通过使用Object方式不支持;
- Condition能够支持多个等待队列(new 多个Condition对象),而Object方式只能支持一个;
- Condition能够支持超时时间的设置,而Object不支持
参照Object的wait和notify/notifyAll方法,Condition也提供了同样的方法:
针对Object的wait方法
- void await() throws InterruptedException:当前线程进入等待状态,如果其他线程调用condition的signal或者signalAll方法并且当前线程获取Lock从await方法返回,如果在等待状态中被中断会抛出被中断异常;
- long awaitNanos(long nanosTimeout):当前线程进入等待状态直到被通知,中断或者超时;
- boolean await(long time, TimeUnit unit)throws InterruptedException:同第二种,支持自定义时间单位
- boolean awaitUntil(Date deadline) throws InterruptedException:当前线程进入等待状态直到被通知,中断或者到了某个时间
针对Object的notify/notifyAll方法
- void signal():唤醒一个等待在condition上的线程,将该线程从等待队列中转移到同步队列中,如果在同步队列中能够竞争到Lock则可以从等待方法中返回。
- void signalAll():与1的区别在于能够唤醒所有等待在condition上的线程
2. 实现分析
ConditionObject是同步器AbstractQueuedSynchronizer的内部类,因为condition的操作需要获取相关的锁,所以作为同步器的内部类也是合理。每个condition 对象包含这一个队列(等待队列)。该队列作为Condition对象实现通知/等待功能的关键
2.1 等待队列
等待队列是一个FIFO的队列,在队列中每个节点都包含了一个线程引用,该线程就是在Condition对象上等待的线程。如果一个 线程调用了Condition.await() 方法,那么该线程将会释放锁、构造成节点加入等待队列并进入等待状态。
一个Condition包含一个队列,Condition拥有首节点(first waiter)和尾结点(last waiter)。当前线程调用Condition.await() 方法时,将会以当前线程构造节点并将节点从尾部加入等待队列。
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;
如上图所示,Condition拥有首尾节点的引用,新增节点只需要将原有尾结点nextwaiter指向它,并且更新尾结点即可。过程中没有使用CAS保证,原因在于调用await() 方法的线程必定是获取了锁的线程,该过程由锁来保证线程安全
注意的是:我们可以多次调用lock.newCondition()方法创建多个condition对象,也就是一个lock可以持有多个等待队列。而在之前利用Object的方式实际上是指在对象Object对象监视器上只能拥有一个同步队列和一个等待队列,而并发包中的Lock拥有一个同步队列和多个等待队列。示意图如下:
如图所示,ConditionObject是AQS的内部类,因此每个ConditionObject能够访问到AQS提供的方法,相当于每个Condition都拥有所属同步器的引用。
2.2 等待---await()实现原理
调用condition的await() 方法,会使当前线程进入等待队列并释放锁,同时线程状态变为等待状态。当从await() 方法返回时,当前线程一定获取了Condition相关联的锁
如果从队列(同步队列和等待队列)的角度看await() 方法,当调用await() 方法时,相当于同步队列的首节点(当前锁的获取者)移动condition的等待队列中。await() 源码如下:
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 1. 将当前线程包装成Node,尾插入到等待队列中
Node node = addConditionWaiter();
// 2. 释放当前线程所占用的lock,在释放的过程中会唤醒同步队列中的下一个节点
int savedState = fullyRelease(node);
int interruptMode = 0;
// 3. 判断是否在同步队列
while (!isOnSyncQueue(node)) {
// 4. 当前线程进入到等待状态
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 5. 自旋等待获取到同步状态(即获取到lock)
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
// 6. 处理被中断的情况
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
//添加到等待队列
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
//将当前线程包装成Node
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
//尾插入
t.nextWaiter = node;
//更新lastWaiter
lastWaiter = node;
return node;
}
调用该方法的线程成功获取了锁。也就是同步队列的首节点。在第1步中将当前线程构造成节点加入等待队列,在第2步中会释放锁,唤醒同步队列的后继节点。然后进入 while 循环,然后通过第4步将该线程进入等待状态。从while退出有两种:1. 线程被中断 2. 线程进入同步队列。
当等待队列中的节点被唤醒,唤醒的节点线程尝试获取同步状态,见第5步,若不是通过其他线程调用signal() 方法,而是对等待线程进行中断,则会抛出InterrupterException。
2.3 通知
调用condition的signal() 方法,将会唤醒在等待队列的首节点,在 唤醒节点之前,会将节点移到同步队列中
public final void signal() {
//1. 先检测当前线程是否已经获取lock
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//2. 获取等待队列中第一个节点,之后的操作都是针对这个节点
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
调用该方法的前置条件是当前线程必须获取了锁,即isHeldExclusively() 检查方法。当头结点不为null时执行doSignal()方法
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
//1. 将头结点从等待队列中移除
first.nextWaiter = null;
//2. while中transferForSignal方法对头结点做真正的处理
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
首先将头结点从队列中移除,再调用transferForSignal() 方法对头结点做真正的处理
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
//1. 更新状态为0
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
//2.将该节点移入到同步队列中去
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
主要做了两件事:1.将头结点的状态更改为CONDITION;2.调用enq方法,将该节点尾插入到同步队列中
对于 ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL) 这个判断,基本上一直为false,那是不是意味着node的线程永远不会被唤醒?其实不是的,由于node已经加入到同步队列中,那这样在release方法中会对同步队列的线程进行唤醒
在等待队列中被唤醒的线程,将从await() 方法中的while循环中退出,进而调用同步器的accquireQueued() 方法加入到获取同步状态的竞争中。
signalAll() 方法
该方法相当于对等待队列的每个节点均指向一次signal() 方法,效果就是将等待队列中所有节点全部移到同步队列中
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}