使用列表锁定实现生产者消费者问题

时间:2018-10-18 09:58:27

标签: java multithreading

这是作业者的生产者消费者模式的一种实现。下面的实现有什么问题。这会导致死锁。我不明白出了什么问题。

我有一个共享队列

我在同一锁上同步生产者和消费者

private static volatile Queue<Integer> BUFFER = new LinkedList<>();
private static int COUNT = 0;
private static final int SIZE = 1;

public static void main(String[] args) {

    new Thread(() -> {
        while (true) {
            while (BUFFER.size() == SIZE) {
                synchronized (BUFFER) {
                    try {
                        System.out.println("Producer waiting");
                        BUFFER.wait();
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }

            synchronized (BUFFER) {
                System.out.println("Producer added : " + COUNT);
                BUFFER.offer(COUNT++);
                System.out.println("Producer notify");
                BUFFER.notify();
            }

            try {
                Thread.sleep(500l);
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }).start();

    new Thread(() -> {
        while (true) {
            while (BUFFER.isEmpty()) {
                synchronized (BUFFER) {
                    try {
                        System.out.println("Consumer waiting");
                        BUFFER.wait();
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }

            synchronized (BUFFER) {
                System.out.println("Consumer consumed : "+ BUFFER.poll());
                BUFFER.notify();
            }
            try {
                Thread.sleep(500l);
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }).start();

}

}

这段代码会在一段时间后导致死锁。

Producer added : 0
Producer notify
Consumer consumed : 0
Consumer waiting
Producer added : 1
Producer notify
Consumer consumed : 1
Producer added : 2
Producer notify
Consumer waiting
Producer waiting

3 个答案:

答案 0 :(得分:0)

您的代码容易受到丢失通知的影响。这是可能发生的情况:

  1. 使用者检查缓冲区是否为空,并且为空。
  2. 生产者将一个项目放入缓冲区并调用BUFFER.notify()
  3. 使用者调用BUFFER.wait()(因为缓冲区在检查时为空)。
  4. 生产者检查缓冲区是否已满,并且 已满(BUFFER.size() == 1)。
  5. 生产者致电BUFFER.wait()

每个线程现在都在等待另一个线程做某事:死锁!

步骤2中的通知丢失。如果另一个线程尚未等待,BUFFER.notify()根本不执行任何操作。


实际上只有一种正确的方法来使用wait()notify()。您可以在这里阅读有关内容:https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

答案 1 :(得分:0)

为生产者消费者使用notify是不正确的方法。 如果在调用notify()时没有人真正在等待该通知,则通知会丢失。

您应该使用wait()。而不是使用notify()semaphore

共享的semaphore可以被任何线程释放和获取,并且是在这种类型的线程间通信中使用的正确工具。

在这种情况下,消费者将从队列中消费,release()semaphore中消费。生产者将acquire()信号量并将某些内容推入队列。 如果队列已满,则生产者将在尝试获取semaphore时阻塞,直到消费者清空队列。 与您基本上在这里使用的条件变量不同,通过使用wait()notify()semaphore保持状态。

有关Udemy的“ Java多线程,并发和性能优化”课程中的绑定队列和未绑定队列的示例均进行了深入讨论。

它说明了何时使用条件变量以及何时以及如何使用信号量。

我希望对您有帮助

答案 2 :(得分:0)

  

以下实现有什么问题。这会导致死锁。我不明白出了什么问题。

while (BUFFER.isEmpty()) {
    synchronized (BUFFER) {

您的代码很接近,但是synchronized关键字必须在此处包含while循环才能起作用,否则它将受到@SolomonSlow列举的竞争条件的影响。这不是并发集合,尽管您将其标记为volatile,但不能保证isEmpty()方法将返回true,可能会将元素添加到列表中,然后使用者输入{{ 1}}块。您需要确保测试和等待发生在同一锁中。

一旦移动了synchronized关键字,就可以删除synchronized,因为对volatile的所有访问都在BUFFER内部。

synchronized

通过将同步移到两个同时,您的代码似乎对我有用。

其他评论:

  • 您可以将synchronized (BUFFER) { while (BUFFER.size() == SIZE) { ... synchronized (BUFFER) { while (BUFFER.isEmpty()) { System.out.println()调用包含在原始notify()中。那里不需要2个街区。
  • 请注意不要离开synchronized和TODO。
  • 您不应捕获e.printStackTrace();,而应使其尽可能地精细。 Exception
  • 当您抓到catch InterruptedException时,应根据策略始终重新中断线程。

    InterruptedException