java并发:多生产者一个消费者

时间:2012-04-04 07:25:32

标签: java concurrency consumer producer

我遇到一种情况,其中不同的线程填充队列(生产者)和一个消费者从该队列中检索元素。我的问题是,当从队列中检索到其中一个元素时,会遗漏一些(丢失信号?)。生产者代码是:

class Producer implements Runnable {

    private Consumer consumer;

    Producer(Consumer consumer) { this.consumer = consumer; }

    @Override
public void run() {
    consumer.send("message");
  }
}

并创建并运行:

ExecutorService executor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 20; i++) {
  executor.execute(new Producer(consumer));
}

消费者代码是:

class Consumer implements Runnable {

private Queue<String> queue = new ConcurrentLinkedQueue<String>();

void send(String message) {
    synchronized (queue) {
        queue.add(message);
        System.out.println("SIZE: " + queue.size());
        queue.notify();
    }
}

@Override
public void run() {
    int counter = 0;
    synchronized (queue) {
    while(true) {
        try {
            System.out.println("SLEEP");
                queue.wait(10);
        } catch (InterruptedException e) {
                Thread.interrupted();
        }
        System.out.println(counter);
        if (!queue.isEmpty()) {             
            queue.poll();
            counter++;
        }
    }
    }
}

}

当代码运行时,我有时会添加20个元素并检索20个元素,但在其他情况下,检索到的元素少于20.任何想法如何解决?

3 个答案:

答案 0 :(得分:10)

我建议您使用BlockingQueue而不是Queue。 LinkedBlockingDeque可能是你的好选择。

您的代码如下所示:

void send(String message) {
    synchronized (queue) {
        queue.put(message);
        System.out.println("SIZE: " + queue.size());
    }
}

然后你需要

queue.take()
消费者线程上的

这个想法是.take()会阻塞,直到队列中的某个项目可用,然后完全返回一个(这是我认为你的实现受到影响的地方:轮询时缺少通知)。 .put()负责为您执行所有通知。无需等待/通知。

答案 1 :(得分:2)

您的代码中的问题可能是因为您使用的是notify而不是notifyAll。前者只会唤醒一个线程,如果有一个等待锁定。这允许竞争条件,其中没有线程在等待并且信号丢失。通知要求所有线程都被唤醒以检查是否可以获得锁定,notifyAll将以较小的性能成本强制执行正确性。

最好在Effective Java 1st ed中解释(参见第150页)。第二版删除了这个提示,因为程序员应该使用java.util.concurrent,它提供了更强的正确性保证。

答案 2 :(得分:2)

同时使用ConcurrentLinkedQueue和同步似乎是个坏主意。它首先违背了并发数据结构的目的。

ConcurrentLinkedQueue数据结构没有问题,用BlockingQueue替换它会解决问题,但这不是根本原因。

问题在于queue.wait(10)。这是定时等待方法。一旦经过10ms,它将再次锁定。

  1. 通知(queue.notify())将丢失,因为如果已经过了10毫秒,则没有消费者线程等待它。

  2. 生产者将无法添加到队列中,因为他们无法获得锁定,因为消费者会再次声明锁定。

  3. 转移到BlockingQueue解决了您的问题,因为您删除了wait(10)代码并且等待并通知由BlockingQueue数据结构处理。