首页 > 解决方案 > 为什么LinkedBlockingQueue的put()中有一个while循环

问题描述

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 时,没有线程可以增加“计数”。

标签: javaconcurrencyjava.util.concurrentblockingqueue

解决方案


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

当您调用 时await,您正在释放与¹关联的Condition。没有办法绕过它,否则,没有人可以获取锁,满足条件并调用signal它。

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

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

所以到达的另一个线程put可以在发出信号的线程之前获得锁,发现有空间,并存储元素而无需打扰信号。然后,当信号线程获得锁时,条件不再满足。所以一个有信号的线程永远不能仅仅因为它接收到一个信号就依赖条件的有效性,因此必须重新检查条件,await如果没有满足则再次调用。

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

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

¹ 需要强调的是,当持有多个锁时,只会释放与条件关联的锁。


推荐阅读