JDK1.8源码解读之 Condition

282 阅读11分钟

前言

  • {@code Condition}将{@code Object}监视方法({@link Object#wait()wait},{@link Object#notify notify}和{@link Object#notifyAll notifyAll})分解为不同的对象,以产生通过与任意{@link Lock}实现结合使用,每个对象具有多个等待集的效果。
  • 如果{@code Lock}替换了{@code sync}方法和语句的使用,则{@code Condition}替换了Object监视器方法的使用。
  • 条件(也称为条件队列或条件变量)为一个线程暂停执行(“等待”)直到另一线程通知某些状态条件现在可能为真提供了一种方法。
  • 由于对该共享状态信息的访问发生在不同的线程中,因此必须对其进行保护,因此某种形式的锁与该条件相关联。
  • 等待条件提供的关键属性是它自动释放关联的锁并挂起当前线程,就像{@code Object.wait}一样。
  • {@code Condition}实例本质上绑定到锁。
  • 要获取特定{@link Lock}实例的{@code Condition}实例,请使用其{@link Lock#newCondition newCondition()}方法。
  • 例如,假设我们有一个有界缓冲区,它支持{@code put}和{@code take}方法。
  • 如果尝试在空缓冲区上执行{@code take},则线程将阻塞直到有可用项为止;否则,该线程将阻塞。
  • 如果在完整的缓冲区上尝试使用{@code put},则线程将阻塞,直到有可用空间为止。
  • 我们希望在单独的等待集中继续等待{@code put}线程和{@code take}线程,以便我们可以使用仅在缓冲区中的项目或空间可用时才通知单个线程的优化。
  • 可以使用两个{@link Condition}实例来实现。
 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();
    }
  }
}
  • ({@link java.util.concurrent.ArrayBlockingQueue}类提供了此功能,因此没有理由实现该示例用法类。
  • ){@code Condition}实现可以提供与行为和语义不同的行为和语义。
  • {@code Object}监视方法,例如对通知的保证顺序,或在执行通知时不需要锁定。
  • 如果实现提供了这种专门的语义,则实现必须记录这些语义。
  • 请注意,{@code Condition}实例只是普通对象,它们本身可以在{@code sync}语句中用作目标,并且可以拥有自己的监视器{@link Object#wait wait}和{@link Object#notify通知}方法。
  • 获取{@code Condition}实例的监视器锁定或使用其监视方法与获取与该{@code Condition}关联的{@link Lock}或使用其{@linkplain #await等待没有特定的关系。
  • }和{@linkplain #signal signalling}方法。
  • 建议避免混淆,除非可能在它们自己的实现中,否则不要以这种方式使用{@code Condition}实例。
  • 除非另有说明,否则为任何参数传递{@code null}值都会导致抛出{@link NullPointerException}。
  • 实现注意事项通常,在等待{@code Condition}时,允许进行“虚假唤醒”,作为对底层平台语义的让步。
  • 这对大多数应用程序几乎没有实际影响,因为应该始终在循环中等待{@code Condition},以测试正在等待的状态谓词。
  • 一个实现可以自由地消除虚假唤醒的可能性,但是建议应用程序程序员始终假定它们会发生,因此总是在循环中等待。
  • 条件等待的三种形式(可中断,不可中断和定时)在它们在某些平台上的实现容易程度和性能特征上可能有所不同。
  • 特别是,可能很难提供这些功能并维护特定的语义,例如排序保证。
  • 此外,中断线程的实际挂起的能力可能并不总是在所有平台上都可行。
  • 因此,不需要实现为所有三种等待形式定义完全相同的保证或语义,也不需要支持中断线程的实际挂起。
  • 需要一个实现来清楚地记录每个等待方法提供的语义和保证,并且当实现确实支持中断线程挂起时,则它必须服从此接口中定义的中断语义。
  • 由于中断通常意味着取消,并且通常不经常进行中断检查,因此与正常方法返回相比,实现可能更喜欢响应中断。
  • 即使可以证明中断发生在另一个可能解除线程阻塞的操作之后,也是如此。
  • 实现应记录此行为。

源码

package java.util.concurrent.locks;
public interface Condition {

    /**
     * 使当前线程等待,直到收到信号或{@linkplain Thread#interrupt interrupted}。
     * 与该{@code Condition}关联的锁被原子释放,并且当前线程出于线程调度目的而被禁用,并且处于休眠状态,直到发生四件事之一:另一个线程为此{@code调用{@link #signal}方法条件},而当前线程恰好被选为要唤醒的线程;或者其他一些线程为此{@code Condition}调用{@link #signalAll}方法;或当前线程的某个其他线程{@linkplain Thread#interrupt interrupts},并且支持中断线程挂起;或发生“虚假唤醒”。
     * 在所有情况下,在此方法可以返回之前,当前线程必须重新获取与此条件关联的锁。
     * 当线程返回时,可以保证保持此锁。
     * 如果当前线程:在进入此方法时设置了其中断状态;或者在支持等待和线程暂停中断的过程中被{@linkplain Thread#interrupt interrupted中断},则抛出{@link InterruptedException}并清除当前线程的中断状态。
     * 在第一种情况下,没有规定在释放锁之前是否进行了中断测试。
     * 实现注意事项当调用此方法时,假定当前线程持有与此{@code Condition}关联的锁。
     * 由实现来确定是否是这种情况,如果不是,则如何确定。
     * 通常,将引发异常(例如{@link IllegalMonitorStateException}),并且实现必须记录该事实。
     * 与响应信号的正常方法返回相比,实现可能更喜欢对中断做出响应。
     * 在那种情况下,实现必须确保将信号重定向到另一个等待线程(如果有)。
     */
    void await() throws InterruptedException;

    /**
     * 使当前线程等待,直到发出信号。
     * 与该条件相关联的锁被原子释放,并且当前线程出于线程调度目的而被禁用,并且处于休眠状态,直到发生以下三种情况之一:其他一些线程为此{@code Condition}调用{@link #signal}方法,并且当前线程恰好被选为要唤醒的线程;或者其他一些线程为此{@code Condition}调用{@link #signalAll}方法;或发生“虚假唤醒”。
     * 在所有情况下,在此方法可以返回之前,当前线程必须重新获取与此条件关联的锁。
     * 当线程返回时,可以保证保持此锁。
     * 如果当前线程进入此方法时已设置其中断状态,或者在等待时{@linkplain Thread#interrupt interrupted},它将继续等待直到发出信号。
     * 当它最终从该方法返回时,其中断状态仍将被设置。
     * 实现注意事项当调用此方法时,假定当前线程持有与此{@code Condition}关联的锁。
     * 由实现来确定是否是这种情况,如果不是,则如何确定。
     * 通常,将引发异常(例如{@link IllegalMonitorStateException}),并且实现必须记录该事实。
     */
    void awaitUninterruptibly();

    /**
     * 使当前线程等待,直到发出信号或中断它,或者经过指定的等待时间。
     * 与该条件相关联的锁被原子释放,并且当前线程出于线程调度目的而被禁用,并且处于休眠状态,直到发生五件事之一:其他一些线程为此{@code Condition}调用{@link #signal}方法,并且当前线程恰好被选为要唤醒的线程;或者其他一些线程为此{@code Condition}调用{@link #signalAll}方法;或当前线程的某个其他线程{@linkplain Thread#interrupt interrupts},并且支持中断线程挂起;或经过指定的等待时间;或发生“虚假唤醒”。
     * 在所有情况下,在此方法可以返回之前,当前线程必须重新获取与此条件关联的锁。
     * 当线程返回时,可以保证保持此锁。
     * 如果当前线程:在进入此方法时设置了其中断状态;或者在支持等待和线程暂停中断的过程中被{@linkplain Thread#interrupt interrupted中断},则抛出{@link InterruptedException}并清除当前线程的中断状态。
     * 在第一种情况下,没有规定在释放锁之前是否进行了中断测试。
     * 给定返回时提供的{@code nanosTimeout}值,该方法将返回等待等待的纳秒数的估计值;如果超时,则返回小于或等于零的值。
     * 该值可用于确定在等待返回但仍不满足等待条件的情况下是否重新等待以及等待多长时间。
     * 此方法的典型用法采用以下形式:
     * boolean aMethod(long timeout, TimeUnit unit) {
     *   long nanos = unit.toNanos(timeout);
     *   lock.lock();
     *   try {
     *     while (!conditionBeingWaitedFor()) {
     *       if (nanos <= 0L)
     *         return false;
     *       nanos = theCondition.awaitNanos(nanos);
     *     }
     *     // ...
     *   } finally {
     *     lock.unlock();
     *   }
     * }}
     *
     *
     * 设计说明:此方法需要一个纳秒参数,以避免在报告剩余时间时出现截断错误。
     * 这样的精度损失将使程序员难以确保总的等待时间不会系统地短于重新等待发生时指定的时间。
     * 实现注意事项当调用此方法时,假定当前线程持有与此{@code Condition}关联的锁。
     * 由实现来确定是否是这种情况,如果不是,则如何确定。
     * 通常,将引发异常(例如{@link IllegalMonitorStateException}),并且实现必须记录该事实。
     * 与正常方法返回响应信号相比,或者与指示经过指定的等待时间相比,实现可能更喜欢对中断做出响应。
     * 无论哪种情况,实现都必须确保将信号重定向到另一个等待线程(如果有)。
     */
    long awaitNanos(long nanosTimeout) throws InterruptedException;

    /**
     * 使当前线程等待,直到发出信号或中断它,或者经过指定的等待时间。
     * 此方法在行为上等效于:{@code awaitNanos(unit.toNanos(time))> 0}
     */
    boolean await(long time, TimeUnit unit) throws InterruptedException;

    /**
     * 使当前线程等待,直到发出信号或被中断或指定的截止时间过去为止。
     * 与该条件相关联的锁被原子释放,并且当前线程出于线程调度目的而被禁用,并且处于休眠状态,直到发生五件事之一:其他一些线程为此{@code Condition}调用{@link #signal}方法,并且当前线程恰好被选为要唤醒的线程;或者其他一些线程为此{@code Condition}调用{@link #signalAll}方法;或当前线程的某个其他线程{@linkplain Thread#interrupt interrupts},并且支持中断线程挂起;或指定的期限已过;或发生“虚假唤醒”。
     * 在所有情况下,在此方法可以返回之前,当前线程必须重新获取与此条件关联的锁。
     * 当线程返回时,可以保证保持此锁。
     * 如果当前线程:在进入此方法时设置了其中断状态;或者在支持等待和线程暂停中断的过程中被{@linkplain Thread#interrupt interrupted中断},则抛出{@link InterruptedException}并清除当前线程的中断状态。
     * 在第一种情况下,没有规定在释放锁之前是否进行了中断测试。
     * 返回值指示期限是否已过,可以按以下方式使用:
     * boolean aMethod(Date deadline) {
     *   boolean stillWaiting = true;
     *   lock.lock();
     *   try {
     *     while (!conditionBeingWaitedFor()) {
     *       if (!stillWaiting)
     *         return false;
     *       stillWaiting = theCondition.awaitUntil(deadline);
     *     }
     *     // ...
     *   } finally {
     *     lock.unlock();
     *   }
     * }}
     *
     * 实现注意事项当调用此方法时,假定当前线程持有与此{@code Condition}关联的锁。
     * 由实现来确定是否是这种情况,如果不是,则如何确定。
     * 通常,将引发异常(例如{@link IllegalMonitorStateException}),并且实现必须记录该事实。
     * 与正常方法返回响应信号相比,或者与指示经过指定的截止日期相比,实现可能更喜欢对中断做出响应。
     * 无论哪种情况,实现都必须确保将信号重定向到另一个等待线程(如果有)。
     *
     */
    boolean awaitUntil(Date deadline) throws InterruptedException;

    /**
     * 唤醒一个等待线程。
     * 如果有任何线程在这种情况下等待,则选择一个线程进行唤醒。
     * 然后,该线程必须重新获取锁,然后才能从{@code await}返回。
     * 实现注意事项在调用此方法时,实现可能(并且通常确实)要求当前线程持有与此{@code Condition}关联的锁。
     * 实现必须记录此先决条件,以及如果未持有锁,则应采取的任何措施。
     * 通常,将引发诸如{@link IllegalMonitorStateException}之类的异常。
     *
     */
    void signal();

    /**
     * 唤醒所有等待的线程。
     * 如果有任何线程在这种情况下等待,那么它们都将被唤醒。
     * 每个线程必须重新获取锁,然后才能从{@code await}返回。
     * 实现注意事项在调用此方法时,实现可能(并且通常确实)要求当前线程持有与此{@code Condition}关联的锁。
     * 实现必须记录此先决条件,以及如果未持有锁,则应采取的任何措施。
     * 通常,将引发诸如{@link IllegalMonitorStateException}之类的异常。
     *
     */
    void signalAll();
}