Java并发编程(五)--Condition

简介

任意一个java.lang.Object都有一组监视器方法wait()、notify()、notifyAll(),通过这些方法和synchronized关键字配合使用可以实现等待/通知机制。通过这种机制可以轻而易举的实现消费者、生产者模式。具体实现搜索一下有很多
Condition是在java1.5中引入的,将这些对象与任意 Lock 实现组合使用,可以为每个对象提供多个等待集合。相比Object实现的监视器方法,Condition接口的监视器方法具有一些Object所没有的特性:

  • Condition接口可以支持多个等待队列,一个Lock实例可以绑定多个Condition,所以可以支持多个等待队列了
  • Condition接口支持响应中断,可以抛出InterruptedException
  • Condition接口支持当前线程释放锁并进入等待状态到将来的某个时间,也就是支持定时功能

例子

如下代码是消费者生产者模式,lock创建了两个Condition对象 notFull,notEmpty。
生产方法put先加锁,然后循环等待notFull Condition。当items 数组未满时,插入数据,最后唤醒在notEmpty上等待的线程,释放锁。
消费方法take先加锁,然后循环等待notEmpty Condition,当items数组不为空时,获取数据,最后唤醒在notFull上等待的线程,释放锁。
await()方法会释放锁资源直到被唤醒重新获取被释放的资源,所以put get方法不会死锁。

class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 

   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length) 
         notFull.await();
       items[putptr] = x; 
       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 x = items[takeptr]; 
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally {
       lock.unlock();
     }
   } 
 }

ReentrantLock.newCondition()

我们以ReentrantLock讲解Condition
ReentrantLock.newCondition()方法使用的是内部类Sync的newCondition()

   public Condition newCondition() {
        return sync.newCondition();
    }

Sync的newCondition方法则直接new一个AQS的内部类ConditionObject返回

final ConditionObject newCondition() {
            return new ConditionObject();
}

ConditionObject结构

ConditionObject实现了Condition接口,和AQS一样,内部维护一个FIFO队列保存等待线程。并且没有state属性。

 public class ConditionObject implements Condition, java.io.Serializable {
        private static final long serialVersionUID = 1173984872572414699L;
        /** First node of condition queue. */
        private transient Node firstWaiter;
        /** Last node of condition queue. */
        private transient Node lastWaiter;
}        

Node类包含三个指针,prev next组成AQS双向 FIFO队列,nextWaiter组成Condition 单向的FIFO队列

await源码

await大致包含四个步骤
1.获取当前线程占有资源数
2.释放当前所有资源
3.等待直至被唤醒
4.重新获取步骤1保存的资源数
awaitUninterruptibly() await() 区别在于awaitUninterruptibly不管线程是否被中断,await方法如果线程被中断会抛出InterruptedException
awaitUninterruptibly更简单,我们来看awaitUninterruptibly源码

 public final void awaitUninterruptibly() {
            Node node = addConditionWaiter();//添加一个Condition 等待节点
            int savedState = fullyRelease(node);//释放所有资源
            boolean interrupted = false;
            while (!isOnSyncQueue(node)) {//如果节点没被转到AQS队列,继续休眠
                LockSupport.park(this);
                if (Thread.interrupted())
                    interrupted = true;
            }
            //重新获取资源
            if (acquireQueued(node, savedState) || interrupted)
                selfInterrupt();
        }

addConditionWaiter()和fullyRelease(node)比较简单

  • addConditionWaiter在Condition FIFO队列尾部新增一个节点
  • fullyRelease(node)是否所有资源,并返回资源数
    acquireQueued方法是AQS的方法,在第一章已经讲解过,可以回见第一章AQS

我们来看下isOnSyncQueue方法

 /**
     * 如果一个节点最初在Conditon等待队列上,现在在AQS的等待队列上则返回true
     */
    final boolean isOnSyncQueue(Node node) {
        //当前节点为Node.CONDITION状态,说明还未被唤醒
        //node.prev == null说明已经在AQS队列头,已经持有资源
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        //如果node.next不为空,说明已经在AQS队列上    
        if (node.next != null) // If has successor, it must be on queue
            return true;
        //从AQS FIFO队列尾部开始匹配当前节点是否在AQS队列中
        return findNodeFromTail(node);
    }
    private boolean findNodeFromTail(Node node) {
        Node t = tail;//获取AQS尾节点
        for (;;) {//遍历整个AQS FIFO队列
            if (t == node) //当前节点在AQS 队列中,返回true
                return true;
            if (t == null) //当前节点为空,说明已经遍历完了,返回false
                return false;
            t = t.prev;//获取前一节点
        }
    }

至此await主要流程源码就已经讲解完了。下面来看下signal怎么处理的

signal源码

  public final void signal() {
      if (!isHeldExclusively())//当前线程不是锁持有者直接抛出异常
          throw new IllegalMonitorStateException();
      Node first = firstWaiter;//获取condition 头节点
      if (first != null)
          doSignal(first);//唤醒头节点
}
private void doSignal(Node first) {
    do {
        //设置Condition队列头节点为下一节点
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;//如果后续没有节点,设置尾节点为null
    //1.当前节点加入到AQS队列中
    //2.如果1成功,跳出循环
    //3.如果1失败。获取Condition 队列头节点,如果头节点为空跳出循环,不为空继续循环
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);

}
 final boolean transferForSignal(Node node) {
        /*
         * 转变当前节点状态从Node.CONDITION到0,如果失败说明已经被其他线程唤醒
         */
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        //将node添加到AQS队列尾部,并获取前一节点
        Node p = enq(node);
        int ws = p.waitStatus;
        //如果前一节点waitStatus>0即Node.CANCELLED状态,唤醒node对应的线程
        //如果前一节点waitStatus<=0,设置前一节点为Node.SIGNAL状态,如果设置失败,说明前一节点状态  已经改变,唤醒node对应线程,尝试获取锁
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

Condition源码的分析到此就结束了,大体其实还是跟AQS一样,使用一个FIFO队列维持等待顺序。只是一个锁可以new多个Condition,解决了Object.wait只有一个等待队列的问题。

你可能感兴趣的:(java并发编程,java,编程,并发)