为什么在LinkedBlockingQueue的put()中有一个while循环

时间:2019-02-23 15:27:56

标签: java concurrency java.util.concurrent blockingqueue

public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    int c = -1;
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();
    try {
        while (count.get() == capacity) {
            notFull.await();
        }
        enqueue(node);
        c = count.getAndIncrement();
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
}

为什么会有while循环?

所有put线程都被putLock关闭。

等待线程持有putLock时,没有线程可以增加“计数”。

3 个答案:

答案 0 :(得分:4)

await有一个基本属性(它也适用于通过synchronized并使用Object.wait进行的内部锁定),您必须了解:

当您调用await时,您正在释放该锁{em> 。无法绕开它,否则,没有人可以获取锁,满足条件并在其上调用Condition

当您的等待线程收到信号时,它不会立即收回锁定。这是不可能的,因为调用signal的线程仍然拥有它。取而代之的是,接收方将尝试重新获取锁,与调用signal并没有太大区别。

但是此线程不一定是试图获取锁的唯一线程。它甚至不必是第一个。在发信号并等待lockInterruptibly()处的锁定之前,另一个线程可能已经到达put。因此,即使锁是公平的(通常不锁),发出信号的线程也没有优先级。即使您为发出信号的线程赋予了优先级,出于不同的原因,也可能会有多个线程被发出信号。

因此,另一个到达lockInterruptibly()的线程可以在发出信号的线程之前获得锁,发现有空间,并且可以存储元素而不必担心信号。然后,到发出信号的线程获取锁时,该条件不再满足。因此,发出信号的线程永远不会仅仅依赖条件的有效性,因为它收到了信号,因此必须重新检查条件并在不满足条件的情况下再次调用put

这使得检查循环条件成为使用the Condition interface中记录的使用await的标准习惯用法,以及使用内部监视器的情况下Object.wait的用法,仅用于完整性。换句话说,这甚至不特定于特定的API。

由于必须在循环中预先检查和重新检查条件,因此规范甚至允许虚假唤醒,即线程从等待操作返回而没有实际接收到信号。这可以简化某些平台的锁实现,而无需更改必须使用锁的方式。

¹必须强调的是,当持有多个锁时,会释放与条件相关的锁。

答案 1 :(得分:0)

当LinkedBlockingQueue的容量已满时,循环的function(*)会阻塞称为put方法的线程。当另一个线程调用take(或poll)方法时,队列中将有空间容纳新元素,并且take方法将发出notFull条件的信号,并且等待中的线程将被唤醒并将该项目放入队列

(*)循环的条件是为了确保不会发生虚假唤醒。

https://en.wikipedia.org/wiki/Spurious_wakeup

答案 2 :(得分:0)

@Holder的回答是正确的,但我想添加有关以下代码和问题的更多细节。

putLock.lockInterruptibly();
try {
    while (count.get() == capacity) {
        notFull.await();
    }
    ...
  

为什么会有while循环?所有的推入线程都被putLock关闭。

while循环是此代码模式的关键部分,它确保从notFull上的信号唤醒线程时,它确保另一个线程没有首先到达那里并重新填充缓冲区

认识到notFull被定义为putLock的条件这一点很重要:

private final Condition notFull = putLock.newCondition();

当线程调用notFull.await()时,它将解锁 putLock,这意味着多个线程可以同时运行notFull.await()。线程只会在调用notFull.signal()(或signalAll())之后尝试重新获取锁。

如果线程A在BLOCKED上试图获取putLock而线程B是WAITING,则竞争条件发生。如果线程C从队列中删除了一些东西并发出信号notFull,则线程B将被从等待队列中取出并放入notFull的阻塞队列中,但不幸的是,它会<在已被阻塞的线程A后面。因此,一旦putLock被解锁,线程A将获取putLock并将某些内容放入队列中,并再次填充它。当线程B最终获得putLock时,它需要再次测试以查看是否仍有可用空间,然后再放入(和溢出)队列。这就是为什么putLock是必需的。

while循环的第二个原因,如@Holder也提到的那样,是为了防止虚假唤醒,这种虚假唤醒在某些情况下会在人为地发出信号时在某些线程体系结构下发生。例如,在某些体系结构下,由于操作系统的限制,在 any 条件下的信号会在 all 条件下发出信号。