为什么 “LinkedBlockingQueue#put” 需要 “notFull.signal()”?

huangapple go评论116阅读模式
英文:

Why does "LinkedBlockingQueue#put" need "notFull.signal()"

问题

以下是LinkedBlockingQueueput方法的源代码翻译部分:

public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    // 注意:在所有的put/take等方法中,惯例是预设本地变量为负,以表示失败,除非被设置为其他值。
    int c = -1;
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();
    try {
        /*
         * 注意,即使count在等待条件中使用,但它并没有受到锁的保护。
         * 这是因为在这一点上,count只能减少(所有其他的put操作都被锁排斥),
         * 如果它从容量值发生变化,我们(或其他等待的put操作)将得到通知。
         * 对于其他等待条件中count的使用也是类似的。
         */
        while (count.get() == capacity) {
            notFull.await();
        }
        enqueue(node);
        c = count.getAndIncrement();
        if (c + 1 < capacity)
            notFull.signal(); // 这是必要的吗?
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
}

关于为什么当前的生产者需要通过notFull.signal()唤醒其他生产者,而消费者在从队列中取走元素后会执行这个操作,可以通过以下例子来解释为什么这是必要的:

假设队列的容量是2,当前队列中已经有2个元素。现在有两个生产者线程 A 和 B,它们都想要将元素放入队列。由于队列已满,它们都进入了等待状态。

在这个时候,如果有一个消费者线程从队列中取走了一个元素,队列中就有一个空位了。但是,这个时候只有一个消费者线程会被唤醒,而另一个消费者线程仍然在等待。如果不唤醒其中一个等待的生产者线程,那么这个空位将一直被浪费,因为没有生产者线程被唤醒来填充它。

因此,当前的生产者在成功放入元素后,如果队列未满(c + 1 < capacity),通过调用notFull.signal()来唤醒其他等待的生产者线程,以便它们有机会继续执行并填充队列。这样可以更充分地利用队列的空间,提高并发性能。

英文:

The source code of put method of LinkedBlockingQueue in JDK 1.8:

public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    // Note: convention in all put/take/etc is to preset local var
    // holding count negative to indicate failure unless set.
    int c = -1;
    Node&lt;E&gt; node = new Node&lt;E&gt;(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();
    try {
        /*
         * Note that count is used in wait guard even though it is
         * not protected by lock. This works because count can
         * only decrease at this point (all other puts are shut
         * out by lock), and we (or some other waiting put) are
         * signalled if it ever changes from capacity. Similarly
         * for all other uses of count in other wait guards.
         */
        while (count.get() == capacity) {
            notFull.await();
        }
        enqueue(node);
        c = count.getAndIncrement();
        if (c + 1 &lt; capacity)
            notFull.signal(); // Is this necessary?
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
}

Why does the current producer need wake up other producers by notFull.signal() when the consumers would do that after they taking elements from the queue? Is there any example can explain this is necessary?

答案1

得分: 0

I'm not sure whether this is possible.

  1. Producer P1, P2(timed) and P3 are blocking at notFull.await();.

  2. Consumer C1 consumers one element and wakes up P1.

  3. P1 is going to put elements in the queue. Meanwhile C2 consumers another element and wakes up P2. Since P1 is holding the putLock, P2 has to wait. Unfortunately, P2 times out while waiting.

  4. P1 needs to wake up P3, otherwise P3 would wait unnecessarily.

英文:

I'm not sure whether this is possible.

  1. Producer P1, P2(timed) and P3 are blocking at notFull.await();.

  2. Consumer C1 consumers one element and wakes up P1.

  3. P1 is going to put elements in the queue. Meanwhile C2 consumers another element and wakes up P2. Since P1 is holding the putLock, P2 has to wait. Unfortunately, P2 times out while waiting.

  4. P1 needs to wake up P3, otherwise P3 would wait unnecessarily.

huangapple
  • 本文由 发表于 2020年9月2日 17:03:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/63702223.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定