Condition详解

来源:互联网 发布:定制t恤 淘宝 编辑:程序博客网 时间:2024/06/08 16:03

Condition详解

JDK的Lock是为了替代synchronized同步锁,Condition是为了替代Object的对象监视器锁。

利用Condition,每个对象可以有多个不同的等待集(wait-sets),这样多个线程可以分别在同一个对象的多个等待集上等待,相对于Object的对象监视器锁,Condition提供了更加精细化的锁操作。

Condition(也可以称为Condition queue,条件队列),如果当前线程在某个条件上等待,当该条件满足时,其他线程可以唤醒该线程,该线程可以继续去竞争锁。

Condition和Lock是绑定的,一个Lock可以关联多个Condition,通过Lock.newCondition()方法获得一个Condition。

Condition的使用

举例说明Conditon的使用:

设想我们现在有一个有界的缓存BoundedBuffer,该缓存提供了put和take方法。如果在一个空的BoundedBuffer上take数据,当前线程将会阻塞,直到该缓存有一项数据可用。如果在一个满的BoundedBuffer上put数据,当前线程也会阻塞,直到该缓存有一个空闲位置可用。通过使用两个Condition,可以分别让put和take的线程在不同的条件下等待,这样我们可以优化通知机制,当缓存不空(或者不满)的时候,只通知那些需要take(或者put)数据的线程,避免了无效通知。

看下BoundedBuffer的实现:

import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/** * <p>文件描述: 有界缓存</p> * * @Author luanmousheng * @Date 17/8/13 上午10:02*/public class BoundedBuffer {    private final Lock lock = new ReentrantLock();    //锁lock上的两个条件    //缓存不满条件    private final Condition notFull = lock.newCondition();    //缓存不空条件    private final  Condition notEmpty = lock.newCondition();    private final Object[] items = new Object[100];    private int putptr = 0;    private int takeptr = 0;    private int count = 0;    public void put(Object item) throws InterruptedException {        lock.lock();        try {            while (count == items.length) {                //等待缓存不满                notFull.await();            }            items[putptr] = item;            if (++putptr == items.length) {                putptr = 0;            }            count++;            //缓存不空,其他取数据阻塞的线程可以被唤醒了            notEmpty.signal();        } finally {            lock.unlock();        }    }    public Object take() throws InterruptedException {        lock.lock();        try {            while (count == 0) {                //等待缓存不空                notEmpty.await();            }            Object res = items[takeptr];            if (++takeptr == items.length) {                takeptr = 0;            }            count--;            //缓存不满,其他添加数据阻塞的线程可以被唤醒了            notFull.signal();            return res;        } finally {            lock.unlock();        }    }}

Condition接口定义

Condition是一个接口,定义了条件需要实现的语义。

public interface Condition {    /**    响应中断的条件等待    */    void await() throws InterruptedException;    /**    不响应中断的条件等待    */    void awaitUninterruptibly();    /**    带超时时间的等待,单位:纳秒    */    long awaitNanos(long nanosTimeout) throws InterruptedException;    /**    带超时时间的等待    */    boolean await(long time, TimeUnit unit) throws InterruptedException;    /**    带到期时间的等待    */    boolean awaitUntil(Date deadline) throws InterruptedException;    /**    唤醒某个在该条件上等待的线程    */    void signal();    /**    唤醒所有在该条件上等待的线程    */    void signalAll();}

Condition的实现

Condition也叫条件队列,AbstractQueuedSynchronizer实现了该接口。以前学习ReentrantLock的时候也说到了AQS,它是JDK锁和并发的核心实现。AQS维护了一个等待队列,如果锁关联了n个Condition,AQS还会维护n个条件队列,队列的节点都是AQS的内部静态类Node。

AQS的内部类ConditionObject实现了Condition接口,看下ConditionObject的定义:

public class ConditionObject implements Condition, java.io.Serializable {    /**    条件队列的第一个节点    */    private transient Node firstWaiter;    /**    条件队列的最后一个节点    */    private transient Node lastWaiter;    //……}

ConditionObject是通过firstWaiter和lastWaiter维护了一个条件队列。

通常我们会使用条件队列的awaitsignal方法,那我们就首先学习await的实现原理:

await方法

public final void await() throws InterruptedException {    //await方法通过抛出中断异常响应中断    if (Thread.interrupted())      throw new InterruptedException();    //包装当前线程,加入到条件队列的队尾    Node node = addConditionWaiter();    //线程阻塞前释放锁    int savedState = fullyRelease(node);    int interruptMode = 0;    //判断该节点是否在等待队列上,如果在等待队列,说明该节点已经被其他线程唤醒并移到了等待队列上    while (!isOnSyncQueue(node)) {      //不在等待队列,继续在条件队列阻塞      LockSupport.park(this);      //interruptMode!=0,说明在该线程唤醒前或者唤醒后被中断过      //响应中断,跳出循环      if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)        break;    }    //在等待队列排队等待获取锁    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)      interruptMode = REINTERRUPT;    //可能存在被取消的节点,删除这些被取消的节点    if (node.nextWaiter != null)       unlinkCancelledWaiters();    //被中断过,响应中断    if (interruptMode != 0)      reportInterruptAfterWait(interruptMode);}

await方法主要做了这么几件事:

  • 将当前线程加入到条件队列的队尾。
  • 释放当前线程已经获取的锁。
  • 自旋检查当前节点是否在等待队列,如果不是则继续挂起,否则说明有其他线程唤醒了该线程,尝试去等待队列获取锁。
  • 被唤醒或者被中断后,在等待队列中排队获取锁。

await主要流程就是这样,让我们再看下详细实现,看下addConditionWaiter方法:

private Node addConditionWaiter() {    Node t = lastWaiter;    //如果条件队列队尾的状态不是CONDITION,可能存在已经取消的节点,删除这些节点    if (t != null && t.waitStatus != Node.CONDITION) {      unlinkCancelledWaiters();      t = lastWaiter;    }    //包装当前线程的节点,添加到条件队列队尾    Node node = new Node(Thread.currentThread(), Node.CONDITION);    if (t == null)      firstWaiter = node;    else      t.nextWaiter = node;    lastWaiter = node;    return node;}

fullyRelease释放当前线程持有的锁。

isOnSyncQueue判断该节点是否在等待队列:

final boolean isOnSyncQueue(Node node) {    //结点状态为CONDITION肯定不在等待队列中    if (node.waitStatus == Node.CONDITION || node.prev == null)      return false;    //存在next节点,说明node肯定在等待队列中    if (node.next != null)       return true;    //从尾往头遍历查找    return findNodeFromTail(node);}

acquireQueued之前在讲ReentrantLock时已经说明过了,这里不再讲解。

signal方法

signal方法唤醒条件队列上某个节点,并将该节点从条件队列移到等待队列。

public final void signal() {    //当前线程持有锁才能唤醒其他线程,否则抛出监视器异常    if (!isHeldExclusively())      throw new IllegalMonitorStateException();    Node first = firstWaiter;    //条件队列不为空    if (first != null)      //主要逻辑都在这里      doSignal(first);}

这断代码的重点是doSignal方法:

private void doSignal(Node first) {    do {      if ( (firstWaiter = first.nextWaiter) == null)        lastWaiter = null;      first.nextWaiter = null;    } while (!transferForSignal(first) &&             (first = firstWaiter) != null);}

doSignal方法遍历条件队列,直到找到第一个未取消的节点。找到第一个未取消的节点后将该节点移到等待队列,然后退出该方法。

看下transferForSignal方法:

//将节点从条件队列移到等待队列final boolean transferForSignal(Node node) {    //原子的更新waitStatus为0    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))      return false;    //将该节点添加到等待队列    //返回当前节点的前置节点    Node p = enq(node);    int ws = p.waitStatus;    //如果前置节点已取消或者设置状态为SIGNAL失败,则唤醒当前节点线程    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))      LockSupport.unpark(node.thread);    return true;}

该方法将节点从条件队列移到等待队列。

signalAll方法

该方法唤醒条件队列中的所有节点线程,并将这些节点从条件队列移到等待队列。

public final void signalAll() {    if (!isHeldExclusively())      throw new IllegalMonitorStateException();    Node first = firstWaiter;    if (first != null)      doSignalAll(first);}

主要看下doSignalAll方法:

//将first及其之后的节点从条件队列移到等待队列private void doSignalAll(Node first) {    lastWaiter = firstWaiter = null;    do {      Node next = first.nextWaiter;      //GC      first.nextWaiter = null;      //将节点移到等待队列      transferForSignal(first);      first = next;      //当节点不为空时,转移节点    } while (first != null);}

这断代码逻辑比较简单,遍历条件队列,调用transferForSignal方法,将节点从条件队列移到等待队列。

以上就是条件队列的主要逻辑。