任意一个java对象,都拥有一组监视器方法(定义在java.lang.Object上),主要包括wait()、wait(long
timeOut)、notify()、notifyAll()方法,这些方法与synchronized同步关键字配合,可以实现等待/通知模式.condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式,但是这俩者在使用方法以及功能特性上还是有差别的
以上摘自《java 并发编程的艺术》5.6节
condition相对于Object监视器方法最大的优势在于, condition能设置多个等待队列,这意味着利用condition能够设计出更加灵活、性能更好的数据结构
例如:阻塞队列ArrayBlockingQueue
//基于lock接口的显示锁
final ReentrantLock lock;
//针对消费者的等待队列
private final Condition notEmpty;
//针对生产者的等待队列
private final Condition notFull;
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
//重点看这里,通过同一个监视器锁创建出俩个不同的等待队列
//这样可以根据不同的条件判断,来精准的使用具体的等待队列,避免不必要的线程等待唤醒操作
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
//看下具体的使用,生产者生产对象
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//重点看这里,如果判断队列已满,那么通知该生产者线程等待
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
private void enqueue(E x) {
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
//重点看这里,精准的唤醒一个消费者线程,上面的生产者线程不受影响
//如果使用notify()的话,并不知道唤醒的是生产者线程还是消费者线程
//如果使用notifyAll()唤醒所有线程,又会造成大量的锁竞争,资源浪费
notEmpty.signal();
}
接下来进入正题,看下condition是如何实现的,先来看下condition接口定义了哪些方法
//相当于Object.wait(),设置当前线程处于等待状态,响应中断,抛出InterruptedException
void await() throws InterruptedException;
//await 忽略中断
void awaitUninterruptibly();
//await 加上超时机制,响应中断,返回 nanosTimeout-消耗时间,如果返回<=0,说明已超时
long awaitNanos(long nanosTimeout) throws InterruptedException;
//await 加上超时机制,并且可以指定时间单位,响应中断
boolean await(long time, TimeUnit unit) throws InterruptedException;
//await 指定时间界限,在此时间前被通知唤醒则返回true,否则为false,响应中断
boolean awaitUntil(Date deadline) throws InterruptedException;
//唤醒一个等待线程,前提是正在执行该方法的线程已经获取了condtion相对应的锁
void signal();
//唤醒所有等待线程
void signalAll();
AQS的内部类ConditionObject实现了condition接口
//condtion 和同步锁队列是共用同一个node对象
//condtion的等待队列中存放了所有调用该condition.await 方法的线程
//当调用condtion.signal 方法,等待队列中的节点将会挪到同步锁队列中
public class ConditionObject implements Condition, java.io.Serializable {
//首节点
private transient Node firstWaiter;
//尾节点
private transient Node lastWaiter;
}
//在看下node中 condtion特有的参数
static final class Node {
//针对于condtion节点,初始状态为CONDITION
static final int CONDITION = -2;
//和同步器共用的一个参数,在同步器中代表node的模式(独占、共享)
//condtion中表示指向下一个节点,condtion是一个单向链表
volatile int waitStatus;
}
下面通过图示来表达同步锁队列和 等待队列之间的关联
看具体的源码await
//主要逻辑
//1、在condition队列中添加一个节点,执行Release释放自身同步状态
//2、循环判断该节点是否被转移到锁同步队列,如果没有则挂起等待
//3、如果被中断,则跳出2的循环做出相应响应(这个响应要在重新获取同步状态后才能做)
//第一个过程是在获取同步状态的情况下执行,所以不用考虑并发情况,
//后俩个过程因为已经释放同步状态了,需要考虑并发
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//在condition队列中添加一个节点,状态是condition
Node node = addConditionWaiter();
//执行release,如果失败了,抛出IllegalMonitorStateException
//如果这里成功,则表示该线程已经成功释放了同步状态,下面的流程都需要考虑并发情况了
int savedState = fullyRelease(node);
//这里初始状态为0
//1、当该节点被移到同步锁队列之前中断(signal之前),则interruptMode为THROW_IE,最终会抛出InterruptedException
//2、当该节点在移动同步锁队列之后中断(signal之后),则interruptMode为REINTERRUPT,保留interrupt状态
//3、如果没有被中断,保持原有状态0
int interruptMode = 0;
//循环判断该节点是否被移动到同步锁队列,如果没有则挂起等待
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
//如果是被中断了,则将节点转移到同步锁队列,同时跳出循环
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//进行到这里说明节点已经被转移到同步锁队列了,执行acquireQueued
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
//执行到这里,说明acquire成功,已经重新获取了同步状态
if (node.nextWaiter != null) // clean up if cancelled
//清理condtion等待队列中的CANCELLED节点
unlinkCancelledWaiters();
//如果不为0,说明之前该线程中断过,那么做出相应的响应
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
//判断该节点在同步锁队列中是否存在
final boolean isOnSyncQueue(Node node) {
//当状态为CONDITION时,或者当pre(同步器队列用到的前驱)为null,则说明不存在
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
//next是同步器用到的后继节点,如果不为空,那么该节点就肯定在同步锁队列了
//这里很有趣,下面会具体分析为什么没有node.prev!=null
if (node.next != null)
return true;
//从同步锁队列尾部查找是否有这个节点
return findNodeFromTail(node);
}
isOnSyncQueue方法有个很有趣的地方,if (node.next != null) 可以判断节点肯定已经在同步锁队列了,那么同理node.prev != null 是不是也能证明呢
答案当然不是了,看以下代码
//这个方法是aqs往同步锁队列添加节点
private Node enq(final Node node) {
for (;;) {
Node t = tail;
//如果未初始化队列,创建head,cas赋值
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
//重点在这里,是先把该节点的先驱和尾节点关联上,再使用cas
//如果这个过程中cas失效了,那么该节点就添加失败了,那这个关联当然也就是无效的
//而signal时会调用这个方法,所以不能通过node的pre是否为null来判断节点是否已经成功移到同步锁队列
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
其中还有一个比较重要的方法是checkInterruptWhileWaiting,判断线程有没有收到中断讯号、中断的时机(signal前还是signal后)
private int checkInterruptWhileWaiting(Node node) {
//当检测到线程中断信号则执行transferAfterCancelledWait,否则返回0
//transferAfterCancelledWait逻辑主要判断中断的时机
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
final boolean transferAfterCancelledWait(Node node) {
//如果cas修改状态成功了,说明此时还没有收到signal信号
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
//将该节点转移到同步锁队列
//需要注意的是,虽然执行到这里注定了该线程会被抛出InterruptedException
//但是也要等到获取同步状态之后才能抛出,否则会产生线程安全问题
enq(node);
return true;
}
//上面cas失败说明已经收到signal信号,singnal方法已经执行
//但是也有可能没执行完,也就是节点还正在转移过程中,所以进行自旋等待
//返回false表明该线程只会记录中断状态,不会抛错
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
到这里await基本讲完了,awaitUninterruptibly、awaitNanos原理基本一样就不多描述了,接下来看看signal
public final void signal() {
//这里需要注意,isHeldExclusively是一个空方法,需要用户自己判断正在执行的线程是否已经获得同步
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
//需用用户继承重写,一般的逻辑就是判断当前线程是否获得同步状态,
//这样接下来的操作就是线程安全的,否则会带来线程安全隐患
protected boolean isHeldExclusively() {
throw new UnsupportedOperationException();
}
private void doSignal(Node first) {
do {
//头节点出队,重新设置nextWaiter为头节点
if ( (firstWaiter = first.nextWaiter) == null)
//如果队列为空,设置lastWaiter为null
lastWaiter = null;
//原头节点断开后继关联
first.nextWaiter = null;
//transferForSignal失败表示该节点signal失败,继续下一个节点
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
//如果这里cas失败,表示这个节点状态是cancel,那么直接返回找下一个节点
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//将该节点放入同步锁队列
//注意,这里返回的p是node的前驱节点
Node p = enq(node);
int ws = p.waitStatus;
//如果前驱节点是cancel,或者设置SIGNAL失败,则唤醒本节点处理
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
完