java.util.concurrent.locks.Condition文档说明

【1】Condition接口文档描述

1.Condition类把Object监视器方法(wait,nofify, notifyAll)分解为不同对象,通过与Lock实现类的合并使用,Condition可以产生每个object都有多个等待集的效果在Lock实现类替代synchronized方法或语句块的地方,Condition可以替换Object监视器方法

2.Conditions(也称为条件队列或条件变量),为一个线程提供了挂起执行(等待)的方法,直到另一个线程通知它某些状态条件可能为真。

3.因为对共享状态信息的访问发生在不同线程,该共享信息必须被保护,因此某种形式的锁与condition条件相关联。等待条件提供的关键属性是它(条件-condition)可以原子性释放关联的锁并挂起当前线程,就像Object.wait() 方法那样。

4.condition-条件实例本质上是绑定到锁Lock上的。要获取特定Lock实例的条件对象,使用newCondition() 方法;

5.举个例子(这个例子非常经典-类似消息队列),假设我们有一个支持put和take 方法的有界缓冲区。若尝试对空缓存区执行take操作,则线程阻塞直到缓冲区有元素;若尝试对满缓存区执行put操作,则线程阻塞直到缓存区有可用空间。 我们想要把 执行put方法和task方法阻塞的线程放在单独的等待集合中,以便我们可以使用优化,当缓存区中元素或空间可用时, 仅通知一个线程。

这可以通过使用两个 Condition-条件实例来实现,如下(这段代码非常重要):


   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();// 解锁
       }
     }
   }
   

java.util.concurrent.ArrayBlockingQueue实现了上述例子,无需自己写代码实现)

6.Condition实现类可以提供不同于Object监视器方法的行为和语义,如保证通知顺序,执行通知时无需持有锁。如果一个Condition实现类提供了这些专门语义,则需要记录这些语义。

7.注意到:

  • Condition实例也仅仅是普通对象,所以可以作为 synchronized方法或语句块的目标对象,也可以调用Condition对象的监视器wait() 和 notify方法。获取Condition实例的监视器锁,或使用监视器方法(wait,notify,notifyAll),与获取condition相关的Lock锁或使用其wait或signal方法没有任何关系。墙裂建议为了避免混淆,不要在把 Conditoin实例作为 synchronized的目标对象,除非在它们自己的实现中。

8.Condition实现类注意事项:

在等待条件时,通常允许发生虚假唤醒,作为对底层平台语义的让步。

这对大多数应用程序来说没有影响,因为condition-条件应该始终在循环中等待,以测试等待的状态谓词(变量是否为真)

Conditon实现类可以自由移除虚假唤醒的可能性,但墙裂建议程序员始终假设它可能发生,因此始终在循环中等待

9.3种形式的条件等待(可中断,不可中断,超时)在一些平台的实现难易程度和性能特征有所不同。特别地,可能很难提供这些特性并维护特定语义,如顺序保证。此外,中断一个线程的实际挂起的能力在所有平台并不总是可行的;

10.因此,Condition实现类不需要为以上3种形式的等待精确定义同样的保证和语义。也不需要支持对线程实际挂起的中断。

11.一个Condition实现类需要记录每个wait方法提供的语义和保证,当Condition实现类支持线程挂起时的中断,则它必须遵守本接口定义的中断语义。

12.中断通常意味着取消,并且中断检测不常发生,所以Condition实现类倾向于响应中断而不是方法正常返回。即使证明中断发生在另一动作解除线程阻塞之后发生,也是如此。Condition实现类应该记录这些行为。


【2】Condition方法

【2.1】void await() throws InterruptedException

1.该方法导致当前线程等待,直到收到信号或被中断

2.与此Condition关联的锁被自动释放,且当前线程无法被线程调度器调度(禁用),并处于休眠状态直到以下4种情况之一发生:

  • 情况1,其他线程调用此condition的信号方法(signal),并且当前线程恰好被选择为要被唤醒的线程 ;
  • 情况2 ,其他线程调用此condition的signalAll方法;
  • 情况3 , 其他线程中断了当前线程,(当Condition实现类支持线程挂起时的中断);
  • 情况4, 发生虚假唤醒(?)

3.在所有情况下,在await() 方法从当前线程返回之前,当前线程都必须重新获取与condition关联的锁。当线程返回时,能够保证它持有锁。

4.如果当前线程:

在该方法入口设置了中断状态,或等待时被中断(且condition实现类支持线程挂起时被中断),

则线程将抛出InterruptedException-中断异常,且当前线程的中断状态被清空。

对于第一种情况,在释放锁之前是否进行中断测试没有规定。

5.Condition实现类注意事项:

在本方法被调用时,假设当前线程持有了condition对象关联的锁。

这取决于condition实现类确定是否是这种情况,如果不是,如何响应。通常,会抛出异常(如IllegalMonitorStateException-非法监视器状态异常),且condition实现类会记录该事实。

6.在响应信号过程中,condition实现类倾向于响应中断而不是方法正常返回。在那种情况下,condition实现类必须保证信号被重定向到其他等待线程(即,sinal发给其他线程以唤醒),如果有的话 。

7.抛出异常:

InterruptedException-中断异常,当当前线程被中断(当condition实现类支持线程挂起时中断)


【2.2】void awaitUninterruptibly()

1.导致当前线程等待,直到被发出信号

2.与此contition对象关联的锁被自动释放,当前线程无法被线程调度器调度(禁用), 并休眠直到以下3种情况之一发生

  • 情况1, 其他线程调用了该condition对象的 signal() 方法,且当前线程被选为要唤醒的线程;
  • 情况2,其他线程调用了 该condition对象的 signalAll() 方法;
  • 情况3,发生 虚假唤醒(?);

3.在所有情况下,在awaitUninterruptibly() 方法从当前线程返回之前,当前线程都必须重新获取与condition关联的锁。当线程返回时,能够保证它持有锁。

4.若在该方法入口设置了当前线程的中断状态,或等待时被中断(且condition实现类支持线程挂起时被中断),该线程会继续等待直到接收到信号(不会中断)。当线程最终从该方法返回时,它的中断状态仍然被设置。

5.Condition实现类注意事项:

在本方法被调用时,假设当前线程持有了condition对象关联的锁。

这取决于condition实现类确定是否是这种情况,如果不是,如何响应。通常,会抛出异常(如IllegalMonitorStateException-非法监视器状态异常),且condition实现类会记录该事实。


【2.3】long awaitNanos(long nanosTimeout) throws InterruptedException

1.导致当前线程等待直到接收到信号,或被中断,或经过给定的等待时间。

2.与此contition对象关联的锁被自动释放,当前线程无法被线程调度器调度(禁用), 并休眠直到以下5种情况之一发生

  • 情况1, 其他线程调用了该condition对象的 signal() 方法,且当前线程被选为要唤醒的线程;
  • 情况2,其他线程调用了 该condition对象的 signalAll() 方法;
  • 情况3,其他线程中断了当前线程,(当Condition实现类支持线程挂起时的中断);
  • 情况4,经过给定的等待时间;
  • 情况5,虚假唤醒;

3.在所有情况下,在该方法从当前线程返回之前,当前线程都必须重新获取与condition关联的锁。当线程返回时,能够保证它持有锁。

4.若在该方法入口设置了当前线程的中断状态,或等待时被中断(且condition实现类支持线程挂起时被中断),则线程将抛出InterruptedException-中断异常,且当前线程的中断状态被清空。

对于第一种情况,在释放锁之前是否进行中断测试没有规定。

5.该方法根据给定的超时纳秒数返回剩余等待纳秒数的估计值。若超时,则该值小于或等于0.

该值可以用于确定在等待返回但等待条件不满足的情况下是否重新等待以及重新等待多长时间。典型用法如下:

 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();
   }
 }

设计说明:该方法需要一个纳秒参数,以避免报告剩余时间时出现截断错误。这样的精度丢失将使程序员难以确保系统上的总等待时间不会短于重新等待时的指定值。

6.condition实现类注意事项:

在本方法被调用时,假设当前线程持有了condition对象关联的锁。

这取决于condition实现类确定是否是这种情况,如果不是,如何响应。通常,会抛出异常(如IllegalMonitorStateException-非法监视器状态异常),且condition实现类会记录该事实。

7.在响应信号过程中,condition实现类倾向于响应中断而不是方法正常返回或者超过给定等待时间。在任何情况下,condition实现类必须保证信号被重定向到其他等待线程(即,sinal发给其他线程以唤醒),如果有的话 。

8.参数:

  • nanosTimeout, 最大等待时间的纳秒数;

9.返回:

  • 给定的最大等待时间减去等待花费时间的估计值。正数可以用于该方法的后续调用以完成等待所需时间。该值小于或等于0,表明没有剩余的等待时间了,即无需再等待了。

10.抛出异常

  • InterruptedException-中断异常,如果当前线程被中断(condition实现类支持线程挂起时中断)

【2.4】boolean await(long time, TimeUnit unit) throws InterruptedException

1.导致当前线程等待直到接收到信号,或被中断,或经过给定的等待时间。该方法等价于 awaitNanos(...) 方法

  awaitNanos(unit.toNanos(time)) > 0

2.参数

  • time, 等待的最长时间;
  • unit,time参数的单位,如时、分、秒

3.返回:

  • 若方法返回前可以检测到等待时间已经过去,则返回false;否则返回true;
  • 即等待已超时,返回false,否则返回true;

4.抛出异常:

  • InterruptedException-中断异常,如果当前线程被中断(condition实现类支持线程挂起时中断)

【2.5】boolean awaitUntil(Date deadline) throws InterruptedException

1.导致当前线程等待直到收到信号,或中断,或经过了给定的截止日期。

2.与condition关联的锁会被自动释放,且当前线程会被线程调度器禁用并休眠,直到以下5种情况之一发生:

  • 情况1,其他线程调用signal() 信号方法,且当前线程被选为要唤醒的线程;
  • 情况2,其他线程调用 signalAll()方法;
  • 情况3,其他线程中断了当前线程(condition实现类支持线程挂起时中断);
  • 情况4,经过指定的截止日期;
  • 情况5,发生虚假唤醒;

3.在所有情况下,在该方法从当前线程返回之前,当前线程都必须重新获取与condition关联的锁。当线程返回时,能够保证它持有锁。

4.若在该方法入口设置了当前线程的中断状态,或等待时被中断(且condition实现类支持线程挂起时被中断),则线程将抛出InterruptedException-中断异常,且当前线程的中断状态被清空。

对于第一种情况,在释放锁之前是否进行中断测试没有规定。

5.返回值 表明是否经过了截止期限,可以如下使用:

 boolean aMethod(Date deadline) {
   boolean stillWaiting = true;
   lock.lock();
   try {
     while (!conditionBeingWaitedFor()) {
       if (!stillWaiting)
         return false;
       stillWaiting = theCondition.awaitUntil(deadline);// 等待,返回是否过了截止期限
     }
     // ...
   } finally {
     lock.unlock();
   }
 }

6.实施注意事项

在本方法被调用时,假设当前线程持有了condition对象关联的锁。

这取决于condition实现类确定是否是这种情况,如果不是,如何响应。通常,会抛出异常(如IllegalMonitorStateException-非法监视器状态异常),且condition实现类会记录该事实。

7.在响应信号过程中,condition实现类倾向于响应中断而不是方法正常返回或者超过给定截止时间。在任何情况下,condition实现类必须保证信号被重定向到其他等待线程(即,sinal发给其他线程以唤醒),如果有的话 。

8.参数

  • deadline, 等待的绝对时间;

9.返回

  • 如果经过了等待时间返回false, 否则true;

10.抛出异常:

  • InterruptedException-中断异常,如果当前线程被中断(condition实现类支持线程挂起时中断)

【2.6】void signal()

1.唤醒一个等待线程

2.如果有多个线程在等待condition-条件,则其中一个线程将被选中进行唤醒。那个选中的线程在从await()方法返回之前,必须重新获取锁;

3.实施注意事项

该方法被调用时, condition实现类可能要求(通常要求)当前线程需要持有与condition对象关联的锁;condition实现类必须记录这个先决条件以及在未持有锁的情况下采取的任何动作;通常,会抛出异常,如IllegalMonitorStateException;


【2.7】void signalAll()

1.唤醒所有等待线程;

2.如果有线程在等待该条件,则所有等待线程都会被唤醒; 每个线程在从 await() 方法返回之前必须重新获取锁;

3.condition实现类注意事项

该方法被调用时, condition实现类可能要求(通常要求)当前线程需要持有与condition对象关联的锁;condition实现类必须记录这个先决条件以及在未持有锁的情况下采取的任何动作;通常,会抛出异常,如IllegalMonitorStateException;


【3】方法总结

【3.2】等待方法

condition等待方法列表
方法名描述
void await() throws InterruptedException

该方法导致当前线程等待,直到收到信号或被中断。

出现以下4种情况之一,线程恢复可运行状态:

情况1,其他调用condition.signal(),且该线程被选中作为要唤醒的线程;

情况2,其他线程调用 condition.signalAll(), 唤醒所有等待线程;

情况3,发生中断;

情况4,发送虚假唤醒;

void awaitUninterruptibly();

该方法导致当前线程等待,直到收到信号;

出现以下3种情况之一,线程恢复可运行状态:

情况1,其他调用condition.signal(),且该线程被选中作为要唤醒的线程;

情况2,其他线程调用 condition.signalAll(), 唤醒所有等待线程;

情况3,发送虚假唤醒;

long awaitNanos(long nanosTimeout) throws InterruptedException;

该方法导致当前线程等待,直到收到信号或被中断或经过给定等待时间。

出现以5种情况之一,线程恢复可运行状态:

情况1,其他调用condition.signal(),且该线程被选中作为要唤醒的线程;

情况2,其他线程调用 condition.signalAll(), 唤醒所有等待线程;

情况3,发生中断;

情况4,经过给定的等待时间;

情况5,发送虚假唤醒;

boolean await(long time, TimeUnit unit) throws InterruptedException;导致当前线程等待直到接收到信号,或被中断,或经过给定的等待时间。该方法等价于 awaitNanos(...) 方法
boolean awaitUntil(Date deadline) throws InterruptedException;

导致当前线程等待直到收到信号,或中断,或经过了给定的截止日期。

出现以5种情况之一,线程恢复可运行状态:

情况1,其他调用condition.signal(),且该线程被选中作为要唤醒的线程;

情况2,其他线程调用 condition.signalAll(), 唤醒所有等待线程;

情况3,发生中断;

情况4,经过指定的截止日期;

情况5,发送虚假唤醒;

【3.2】唤醒方法

condition唤醒方法列表
方法名描述
void signal()

唤醒一个等待线程

如果有多个线程在等待condition-条件,则其中一个线程将被选中进行唤醒。那个选中的线程在从await()方法返回之前,必须重新获取锁;

void signalAll()

唤醒所有等待线程

如果有线程在等待该条件,则所有等待线程都会被唤醒; 每个线程在从 await() 方法返回之前必须重新获取锁;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这段代码无法正确执行是因为在调用Condition的signal()方法之前还没有获取到锁。 在代码中,线程A、B、C都会先调用reentrantLock.lock()获取锁,然后进入等待状态,并在等待状态中调用conditionX.await()方法来释放锁并等待被唤醒。 但是,在主线程中启动三个子线程之后,立即调用了condition1.signal()方法来唤醒线程A,这时候线程A还没有开始运行,也没有获取到锁,所以调用signal()方法会抛出IllegalMonitorStateException异常。 为了解决这个问题,可以在启动子线程之后,先调用reentrantLock.lock()获取锁,然后再调用condition1.signal()方法唤醒线程A,最后释放锁。这样可以确保线程A在调用signal()方法之前已经获取到了锁。 修正后的代码如下: ``` package com.company.calcul; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class Thread1 { public static void main(String[] args) { ReentrantLock reentrantLock = new ReentrantLock(); Condition condition1 = reentrantLock.newCondition(); Condition condition2 = reentrantLock.newCondition(); Condition condition3 = reentrantLock.newCondition(); Thread threadA = new Thread(() -> { reentrantLock.lock(); try { condition1.await(); System.out.println("A"); condition2.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { reentrantLock.unlock(); } }); Thread threadB = new Thread(() -> { reentrantLock.lock(); try { condition2.await(); System.out.println("B"); condition3.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { reentrantLock.unlock(); } }); Thread threadC = new Thread(() -> { reentrantLock.lock(); try { condition3.await(); System.out.println("C"); } catch (InterruptedException e) { e.printStackTrace(); } finally { reentrantLock.unlock(); } }); reentrantLock.lock(); try { threadA.start(); threadB.start(); threadC.start(); condition1.signal(); } finally { reentrantLock.unlock(); } } } ``` 在修正后的代码中,主线程在启动子线程之前先获取锁,然后再启动子线程并调用condition1.signal()方法唤醒线程A,最后释放锁。这样就能保证线程A在调用signal()方法之前已经获取到了锁。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值