在java中,我试图使用简单的wait和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();
}
}
答案 0 :(得分: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();
}
}
}
}
}
请注意,在这两种情况下,都会在检查是否可以继续执行的代码和方法的实际业务之间保持锁定。
答案 1 :(得分:1)
我可以看到您当前的实施中存在很多问题。但是,您调查了什么以及您所关注的死锁是由什么引起的?我相信这应该是你做过的事情。
最大的问题之一是同步范围完全错误,并且造成了很多竞争条件。
使用消费者逻辑作为示例,队列中可能只有1个元素。两个消费者线程都命中if (q.isEmpty()) {
并且都认为它有从队列中获取的东西。然后两个都会继续运行q.remove()
,这对于第一个线程来说很好,但是下一个会抛出异常。
竞争条件的另一个例子是,消费者检查队列可能是空的,但是在它启动同步块之前,生产者在队列中放入10个项目使其填满,然后消费者输入同步块为{ {1}}。由于wait()
之前已完成,并且消费者将丢失之前的notifyAll()
,并且因为队列现已满,并且生产者线程不会将任何新项目放入队列,因为它一直在等待队列被某人消费。繁荣,僵局
您的代码中还存在其他问题(例如,未在循环中包装wait())。
我强烈建议您使用谷歌作为生产者 - 消费者队列的一些例子(我相信有很多)并尝试了解什么是正确的方法。
回复@ TedHopp评论中的评论:
@TedHopp的方式会起作用,但不必释放并重新获取队列监视器。
通常情况应该是这样的:
notifyAll()
此外,通常我们想要创建一个producer-consumer-queue类,而这种pc-queue的“add”方法将包含上述同步块中的逻辑。
上述方法不需要额外的监视器释放/重新获取,并且看起来更接近pc-queue中应该实现的内容。
答案 2 :(得分:1)
共享内存上的每个操作都应该防止多线程访问。由于队列检查的不同步状态,当前实现具有死锁。您应该能够在Promela中轻松建模该代码以获取死锁方案。不过你应该知道condition_variables(synchronized部分)没有计数语义,所以如果线程在到达等待状态之前是抢占,而另一个在同时调用notifyAll()函数,那么它将不适用于它之后的preemtioned线程得到控制权。 解决方案非常简单:
...
while (true)
{
synchronized (q)
{
if (q.size() == 10)
...
while (true)
{
synchronized(q)
{
if (q.isEmpty())
...
答案 3 :(得分:0)
您应该在同步中包装任何触及队列的代码。 例如
if (q.size() == 10) {
synchronized (q) {
在评估if语句后,队列大小可能会发生变化。 最好切换订单。
您的消费者中存在同样的问题