有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java为什么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循环

所有的推杆线都被锁死了

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


共 (3) 个答案

  1. # 1 楼答案

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

    (*)循环的条件是确保没有发生虚假唤醒

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

  2. # 2 楼答案

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

    当你调用await时,你是在释放锁这个Condition与\关联。没有办法绕过它,否则,没有人可以获得锁,使条件得到满足,并对其调用signal

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

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

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

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

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

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

  3. # 3 楼答案

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

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

    why is there a while loop? All the putting thread is shut out by putLock.

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

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

    private final Condition notFull = putLock.newCondition();
    

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

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

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