1

在 java 中,我尝试使用下面的代码使用简单的等待和 notifyAll() 方法编写生产者和消费者实现。它运行了几秒钟,然后挂起。任何想法如何解决这个问题。

import java.util.ArrayDeque;
import java.util.Queue;

public class Prod_consumer {
    static Queue<String> q = new ArrayDeque(10);

    static class Producer implements Runnable {
        public void run() {
            while (true) {
                if (q.size() == 10) {
                    synchronized (q) {
                        try {
                            System.out.println("Q is full so waiting");
                            q.wait();
                        } catch (InterruptedException ex) {
                            ex.printStackTrace();
                        }
                    }
                }
                synchronized (q) {
                    String st = System.currentTimeMillis() + "";
                    q.add(st);
                    q.notifyAll();
                }
            }
        }

    }

    static class Consumer implements Runnable {
        public void run() {
            while (true) {
                if (q.isEmpty()) {
                    synchronized(q) {
                        try {
                            System.out.println("Q is empty so waiting ");
                            q.wait();
                        }catch(InterruptedException ie) {
                            ie.printStackTrace();
                        }
                    }
                }
                synchronized(q) {
                    System.out.println(q.remove());
                    q.notifyAll();
                }

            }

        }
    }

    public static void main(String args[]) {
        Thread consumer = new Thread(new Consumer());
        Thread consumer2 = new Thread(new Consumer());
        Thread producer = new Thread(new Producer());

        producer.start();
        consumer.start();
        consumer2.start();

    }

}
4

4 回答 4

2

您的Producer代码似乎可疑。您想等到队列大小低于 10,然后添加下一个元素。但是,按照当前的逻辑,您等待通知,无论原因如何,都不要检查队列是否超出容量,然后释放队列上的锁。然后,您重新锁定队列并添加项目(无论其他线程是否可能已将某些内容放入队列)。

我建议这段代码:

static class Producer implements Runnable {
    public void run() {
        while (true) {
            synchronized (q) {
                if (q.size() < 10) {
                    String st = System.currentTimeMillis() + "";
                    q.add(st);
                    q.notifyAll();
                } else {
                    try {
                        System.out.println("Q is full so waiting");
                        q.wait();
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                }
            }
        }
    }
}

你在课堂上也有类似的问题Consumer。我建议这样做:

static class Consumer implements Runnable {
    public void run() {
        while (true) {
            synchronized (q) {
                if (q.isEmpty()) {
                    try {
                        System.out.println("Q is empty so waiting ");
                        q.wait();
                    }catch(InterruptedException ie) {
                        ie.printStackTrace();
                    }
                } else {
                    System.out.println(q.remove());
                    q.notifyAll();
                }
            }
        }
    }
}

请注意,在这两种情况下,都会在检查是否可以继续执行的代码和方法的实际业务之间保持锁定。

于 2013-07-19T01:53:28.340 回答
1

我可以看到您当前的实施中有很多问题。但是,您调查了什么以及您所关注的死锁是在哪里引起的?我相信这应该是你做过的事情。

最大的问题之一是同步的范围是错误的,它会导致大量的竞争条件。

以您的消费者逻辑为例,队列中可能只有 1 个元素。两个消费者线程都命中if (q.isEmpty()) {并且都认为它可以从队列中获取一些东西。然后两者都将继续运行q.remove(),这对于第一个线程来说很好,但下一个线程会抛出异常。

另一个竞争条件的例子是,有可能消费者检查了队列是空的,但就在它开始同步块之前,生产者将 10 个项目放入队列中使其满了,然后消费者进入同步块wait()。正如notifyAll()之前完成的那样,消费者将失去前一个notifyAll(),并且因为队列现在已满,生产者线程不会将任何新项目放入队列,因为它会继续等待队列被某人消费。繁荣,僵局

您的代码中还有其他问题(例如,没有将 wait() 包装在一个循环中)。

我强烈建议你谷歌一些生产者-消费者队列的例子(我相信有很多),并尝试了解什么是正确的做法。


回复@TedHopp 评论中的评论:

@TedHopp 的方式可行,但没有必要释放和重新获取队列监视器。

通常它应该是这样的:

static class Producer implements Runnable {
    public void run() {
        while (true) {  // keep on adding item
            String st = "" + System.currentTimeMillis(); // prepare the item
            synchronized (q) {
                while (q.size() >= 10) {  // keep on waiting when the queue is full
                    try {
                        System.out.println("Q is full so waiting");
                        q.wait();
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                        // should be properly handled by rethrowing etc.
                    }
                }
                q.add(st);  // add item to queue as it is not full at this moment
                q.notifyAll();
            }
        }
    }
}

此外,通常我们希望创建一个生产者-消费者-队列类,而这种 pc-queue 的“add”方法将包含上述同步块中的逻辑。

上述方式不需要额外释放/重新获取监视器,并且看起来更接近应该在 pc-queue 中实现的内容。

于 2013-07-19T01:51:42.410 回答
1

共享内存上的每个操作都应该防止多线程访问。由于队列检查的不同步状态,当前实现存在死锁。您应该能够轻松地在 Promela 中对该代码进行建模以获得死锁场景。不过你应该知道condition_variables(同步部分)没有计数语义,所以如果线程在到达等待状态之前被抢占,而另一个线程同时调用了notifyAll()函数,那将不适用于它之后的抢占线程取回控制权。解决方案非常简单:

...
while (true)
{
    synchronized (q)
    {
        if (q.size() == 10)
...
while (true)
{
    synchronized(q)
    {
        if (q.isEmpty())
...
于 2013-07-19T02:03:18.547 回答
0

您应该在同步中包装任何接触队列的代码。例如

if (q.size() == 10) {
    synchronized (q) {

评估 if 语句后,队列大小可能会发生变化。最好切换顺序。

您的消费者也有同样的问题

于 2013-07-19T01:52:50.273 回答