Java 为什么LinkedBlockingQueue的put()中有while循环

Java 为什么LinkedBlockingQueue的put()中有while循环,java,concurrency,java.util.concurrent,blockingqueue,Java,Concurrency,Java.util.concurrent,Blockingqueue,为什么会有一个while循环 所有的推杆线都被锁死了 当等待线程持有putLock时,没有线程可以增加“count”。循环的函数*在LinkedBlockingQueue的容量已满时阻止称为put方法的线程。当另一个线程调用take或poll方法时,队列中将有新元素的空间,take方法将发出notFull条件的信号,等待的线程将被唤醒,并可以将项目放入队列 *循环的条件用于确保未发生虚假唤醒 当LinkedBlockingQueue的容量已满时,循环的函数*正在阻止名为put方法的线程。当另一个

为什么会有一个while循环

所有的推杆线都被锁死了


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

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

*循环的条件用于确保未发生虚假唤醒


当LinkedBlockingQueue的容量已满时,循环的函数*正在阻止名为put方法的线程。当另一个线程调用take或poll方法时,队列中将有新元素的空间,take方法将发出notFull条件的信号,等待的线程将被唤醒,并可以将项目放入队列

*循环的条件用于确保未发生虚假唤醒


wait有一个基本属性,它通过synchronized和using Object.wait应用于内在锁定。此外,您必须了解:

当您调用wait时,您正在释放与该条件相关联的锁。无法绕过它,否则,没有人可以获取锁,使条件满足,并调用它的信号

当等待线程收到信号时,它不会立即获得锁。这是不可能的,因为调用信号的线程仍然拥有它。相反,接收方将尝试重新获取锁,这与以可中断方式调用锁没有太大区别

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

因此,到达put的另一个线程可以在发出信号的线程之前获得锁,发现有空间,并存储元素,而不必担心信号。然后,当发出信号的线程获得锁时,该条件不再满足。因此,有信号的线程决不能仅仅因为收到信号就依赖于条件的有效性,因此必须重新检查条件,如果未满足,则再次调用wait

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

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


必须强调的是,当持有多个锁时,只有与该条件相关联的锁才会被释放。

等待有一个基本属性,它通过同步和使用Object应用于内在锁定。同样,你必须理解:

当您调用wait时,您正在释放与该条件相关联的锁。无法绕过它,否则,没有人可以获取锁,使条件满足,并调用它的信号

当等待线程收到信号时,它不会立即获得锁。这是不可能的,因为调用信号的线程仍然拥有它。相反,接收方将尝试重新获取锁,这与以可中断方式调用锁没有太大区别

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

因此,到达put的另一个线程可以在发出信号的线程之前获得锁,发现有空间,并存储元素,而不必担心信号。然后,当发出信号的线程获得锁时,该条件不再满足。因此,有信号的线程决不能仅仅因为收到信号就依赖于条件的有效性,因此必须重新检查条件,如果未满足,则再次调用wait

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

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


必须强调的是,当持有多个锁时,只有与该条件相关联的锁被释放。

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

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循环?所有的推杆线都被锁死了

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

有一点很重要,那就是要认识到notFull被定义为putLock上的一个条件:

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

如果线程A在尝试获取putLock时被阻塞,而线程B在等待notFull,则会发生争用情况。如果线程C从队列中删除了某些内容并发出未满信号,则线程B将从等待队列中取出并放入putLock上的阻塞队列中,但不幸的是,它将位于已被阻塞的线程a后面。所以一旦putLock被解锁,线程A将获取putLock并将一些东西放入队列中,再次填充它。当线程B最终获得putLock时,它需要再次测试,看看在放入和溢出队列之前是否还有可用的空间。这就是为什么while是必要的


正如@Holder所提到的,while循环的第二个原因是为了防止在某些线程体系结构下,当一个条件被人工通知时可能发生的虚假唤醒。例如,在某些体系结构下,由于操作系统的限制,任何条件下的信号都会发出所有条件的信号。

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

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循环?所有的推杆线都被锁死了

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

有一点很重要,那就是要认识到notFull被定义为putLock上的一个条件:

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

如果线程A在尝试获取putLock时被阻塞,而线程B在等待notFull,则会发生争用情况。如果线程C从队列中删除了某些内容并发出未满信号,则线程B将从等待队列中取出并放入putLock上的阻塞队列中,但不幸的是,它将位于已被阻塞的线程a后面。所以一旦putLock被解锁,线程A将获取putLock并将一些东西放入队列中,再次填充它。当线程B最终获得putLock时,它需要再次测试,看看在放入和溢出队列之前是否还有可用的空间。这就是为什么while是必要的


正如@Holder所提到的,while循环的第二个原因是为了防止在某些线程体系结构下,当一个条件被人工通知时可能发生的虚假唤醒。例如,在某些架构下,由于操作系统的限制,任何条件下的信号都会向所有条件发出信号。

我认为只有一个线程在等待,因为notFull.await。一个也没有@Syn多线程可以达到该点。但是当他们收到通知时,只有一个线程可以继续,其他线程必须等待下一个通知,然后再次只有一个线程可以继续。为什么“多个线程可以到达该点”?一个线程可以获取putLock,其他线程等待,因为“putLock.lockinterruptablely”多个线程可以到达该点。见@Holger的答案。notFull是putLock附带的一个条件。当您调用notFull.awat时,这将释放putLock@Syn。@格雷,谢谢。我以前没有使用过条件。我想总有一些东西需要学习。我想只有一个线程在等待,因为没有满。等待。一个也没有@Syn多线程可以达到该点。但是当他们接到通知时,只有一个能够继续,其他人必须等待下一个通知,
然后,只有一个线程将继续。为什么“多个线程可以到达该点”?一个线程可以获取putLock,其他线程等待,因为“putLock.lockinterruptablely”多个线程可以到达该点。见@Holger的答案。notFull是putLock附带的一个条件。当您调用notFull.awat时,这将释放putLock@Syn。@格雷,谢谢。我以前没有使用过条件。我想总有一些东西需要学习。我知道在weakCompareAndSet的情况下,虚假的失败是什么,虚假的唤醒也是这样吗?你知道吗?@Eugene我不知道开发人员想到了哪些具体的实现细节。我模模糊糊地记得读过一些关于Linux线程库的文章,但最后,这并不重要,因为它的工作方式,它可能总是表现得好像可能会发生虚假的唤醒,也就是说,你无论如何都需要这个循环。在某些体系结构上可能会发生虚假唤醒,在这些体系结构中,线程可以在没有实际接收到信号的情况下被唤醒。例如,一些架构在由于实现而发出任何信号时都会唤醒所有条件。需要指出的一点是,notFull是来自putLock的条件,因此当您专门调用notFull.Wait时,这将解锁putLock。我知道你是这么说的,但请你考虑清楚一点。“格雷,我想我知道你要去哪里。”添加了一个明确的语句。我知道weakCompareAndSet的情况下虚假失败是什么,虚假唤醒也是这样吗?你知道吗?@Eugene我不知道开发人员想到了哪些具体的实现细节。我模模糊糊地记得读过一些关于Linux线程库的文章,但最后,这并不重要,因为它的工作方式,它可能总是表现得好像可能会发生虚假的唤醒,也就是说,你无论如何都需要这个循环。在某些体系结构上可能会发生虚假唤醒,在这些体系结构中,线程可以在没有实际接收到信号的情况下被唤醒。例如,一些架构在由于实现而发出任何信号时都会唤醒所有条件。需要指出的一点是,notFull是来自putLock的条件,因此当您专门调用notFull.Wait时,这将解锁putLock。我知道你是这么说的,但请你考虑清楚一点。“格雷,我想我知道你要去哪里。”添加了一个显式语句。
private final Condition notFull = putLock.newCondition();