这个 ConcurrentLinkedQueue/wait/notify 算法正确吗?

Is this ConcurrentLinkedQueue/wait/notify algorithm correct?

我知道通常对于像这样的 producer/consumer 对,应该使用阻塞队列。我在这里只想了解 Java 中更好的内存一致性,并发数据结构和锁之间的交互,以及确定 ConcurrentLinkedQueue.

大小时的不精确性。

问题是,下面的算法是否能确保产生的任何东西都被消耗掉,就像在普通非线程安全队列的情况下一样?注意:我运行它好几次了,它总是这样。

import java.util.concurrent.ConcurrentLinkedQueue;

public class Produce extends Thread {
    @Override
    public void run() {
        synchronized(Main.queue) {
            Main.queue.add(1);
            Main.queue.notifyAll();
        }
    }
}
public class Consume extends Thread {
    @Override
    public void run() {
        synchronized(Main.queue) {
            while(true) {
                while(!Main.queue.isEmpty()) {
                    Main.queue.poll();
                    System.out.println("consumed");
                }
                System.out.println("empty");
                try {
                    Main.queue.wait();
                } catch(InterruptedException e) {
                }
            }
        }
    }
}
public class Main {
    public static final ConcurrentLinkedQueue<Integer> queue =
            new ConcurrentLinkedQueue();
    public static void main(String[] args) {
        (new Consume()).start();
        (new Produce()).start();
    }
}

你的问题的答案是肯定的。消费者将看到所有更新。

但是:

  1. 这不是一个明智的实施。看起来您正在使用 wait / notify 的轮询方法,这样您就不需要繁忙的循环来等待队列变为非空。但更好(更简单、更有效)的方法是使用 BlockingQueue 并使用阻塞 get() 方法。

    就其价值而言,通过使用队列对象作为互斥体来执行等待/通知信号,您正在否定使用 ConcurrentLinkedQueue 的任何 可能 可伸缩性优势。 (如果您使用不同的对象作为互斥锁,这也适用。问题是互斥!)

  2. 如果您要这样做(无论出于何种原因),notify()notifyAll() 更可取。只有一个消费者能够消费您添加到队列中的那个(单个)元素。唤醒所有消费者是不必要的。

  3. 扩展 Thread 不是一个好主意。更好的方法是将业务逻辑放入 Runnable(或 lambda)中,作为 Thread 构造函数参数传递。阅读:"implements Runnable" vs "extends Thread" in Java


您还对以下内容感兴趣:

... what is exactly the imprecision when determining the size of ConcurrentLinkedQueue.

答案在 javadoc for ConcurrentLinkedQueue:

"Beware that, unlike in most collections, this method is NOT a constant-time operation. Because of the asynchronous nature of these queues, determining the current number of elements requires an O(n) traversal."

"Additionally, if elements are added or removed during execution of this method, the returned result may be inaccurate. Thus, this method is typically not very useful in concurrent applications."

换句话说,ConcurrentLinkedQueue 计算个队列元素,如果同时添加和删除元素,则不会给出准确答案。