为什么java.util.concurrent.ArrayBlockingQueue使用'while'循环而不是'if'来调用await()?

ken*_*y_k 13 java concurrency multithreading

我一直在玩我自己的版本,使用'if',似乎一切正常.当然,如果使用signalAll()而不是signal(),这将会崩溃,但如果一次只通知一个线程,那怎么会出错呢?

他们的代码在这里 - 检查put()和take()方法; 可以在JavaDoc for Condition的顶部看到更简单,更多点的实现.

我的实施的相关部分如下.

public Object get() {
    lock.lock();
    try {
        if( items.size() < 1 )
            hasItems.await();
        Object poppedValue = items.getLast();
        items.removeLast();
        hasSpace.signal();
        return poppedValue; 
    } catch (InterruptedException e) {
        e.printStackTrace();
        return null;
    } finally {
        lock.unlock();
    }
}

public void put(Object item) {
    lock.lock();
    try {
        if( items.size() >= capacity )
            hasSpace.await();
        items.addFirst(item);
        hasItems.signal();
        return;
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
}
Run Code Online (Sandbox Code Playgroud)

PS我知道,一般来说,特别是在像这样的lib类中,应该让异常渗透.

Aff*_*ffe 20

防止虚假的唤醒.JVM无法保证线程再次开始运行的唯一可能原因是您按照预期的方式调用了信号.有时它会意外地开始并且去(Spurious wake up).因此,如果您要运行的条件实际上不正确,则必须再次等待.

这在wait方法的javadoc中有解释:http: //java.sun.com/javase/6/docs/api/java/lang/Object.html#wait%28long%29

在等待的文档中提到:http: //java.sun.com/javase/6/docs/api/java/util/concurrent/locks/Condition.html#await%28%29

与此条件关联的锁被原子释放,并且当前线程因线程调度而被禁用,并处于休眠状态,直到发生以下四种情况之一:

  • 一些其他线程为此Condition调用signal()方法,并且当前线程恰好被选为要被唤醒的线程; 要么

  • 其他一些线程为此Condition调用signalAll()方法; 要么

  • 其他一些线程会中断当前线程,并支持线程挂起中断; 要么

*发生"虚假唤醒".

Condition接口的某些实现可能会抑制虚假唤醒,但依赖于此将依赖于实现细节并使您的代码不可移植.

  • 我知道这是一个受欢迎的答案,但我强烈认为这不是正确的答案.虚假唤醒我肯定会发生,但使用时间的原因是因为竞争条件_不是虚假的唤醒.请参阅下面的答案或我提出的关于此的页面:http://256.com/gray/docs/misc/producer_consumer_race_conditions/ (6认同)

Gra*_*ray 14

为什么java.util.concurrent.ArrayBlockingQueue使用'while'循环而不是'if'来调用await()?

他们使用while而不是if防止生产者/消费者模型中的经典线程竞争条件,并防止更罕见的虚假唤醒情况.

当(例如)多个消费者正在等待特定条件(如队列为空)并且通知条件时,另一个线程可能首先锁定并"窃取"添加到队列中的项目.该while循环是线程在尝试将其排队之前确保队列具有项目所必需的.

示例代码

我写了一些示例代码和更多文档来演示竞争条件.

比赛条件的描述

查看您的具体代码,比赛如下:

  1. 线程#1(消费者)处于await()while循环中,等待队列中的项目
  2. 生产者线程#2锁定队列
  3. 线程#3,一个消费者,完成消耗最后一项,调用get(),锁定队列,并且必须等待#2解锁(它等待,hasItems但它正在等待获取lock)
  4. 线程#2,将一个项目添加到队列中并调用hasItems.signal()以通知某人那里有一个项目
  5. 线程#1被唤醒并锁定队列,必须等待#2解锁
  6. 线程#2解锁
  7. 线程#3 线程#1之前等待锁定,因此它首先锁定队列,进入while循环并将#1通知的项目出列,然后解锁
  8. 线程#1现在能够锁定.如果它只是一个if声明,它将继续前进,并试图从一个空的列表中出列,这个列表会抛出ArrayIndexOutOfBoundsException什么东西.

while声明必要的原因是为了处理这些竞争条件.在上面的步骤8中,使用a while,线程#1将循环回到测试以查看队列中是否有项目,发现没有项目,然后返回等待.

这是一个经典的问题,绊倒了很多可重入的程序员.例如,O'Reilly pthreads圣经的初始版本有没有 while循环的示例代码,必须重新发布.

对于某些线程系统,系统更容易唤醒所有条件而不是已发出信号的特定条件,因此可能发生"虚假唤醒".该while循环对这种保护也是如此.

  • 如果前一个消费者正在调用get方法并在正确的时间输入while循环,则代码仍会出现问题.这不是关于通知多个消费者.我怀疑人们得到的大量"虚假唤醒"是这种竞争条件(这不是虚假的),而不是某些操作系统/硬件信号问题. (3认同)
  • 只是澄清答案的评论,虽然已经很清楚了.线程#1和#3都是消费者,但它们并不对称:#1处于"等待"状态,但#3正在等待"get"开头的锁定.因此,当生产者发送一个"信号"时,可以唤醒__only__#1,但可能是第一个获得锁定的#3.如果发生这种情况,#3首先处理该项目,#1在获得锁定时无需处理. (2认同)