Java等待 - 在循环中同步进/出

时间:2014-11-30 22:02:42

标签: java multithreading wait

我正在解决消费者/生产者问题,让自己熟悉Java中的并发问题。我的问题可能与C / P问题本身相邻。在下面的代码片段中,如果我使用的是版本1,程序似乎运行正常,而在版本2中,我似乎在一段时间后会出现死锁。

我只会发布Producer类,因为不需要消费者类。

  • 缓冲区是Producer存放其输出的地方
  • 我只是将1的整数添加到其中
  • while(true)是让线程持续运行

我的问题是:我知道在Java API中,调用wait()的结构是:synchronized - >而 - >等待。但在这种情况下会发生什么:而 - >同步 - >等待?如果在wait()期间,Consumer调用notifyAll()并且Producer再次唤醒,那么代码将不会从调用wait()的地方继续,并最终它会点击while(buffer.remainingCapacity()== 0) ?

我已经开始挖掘,看看以前是否曾经问过这个问题,但是找不到具体的内容。

版本1

public class Producer extends Thread {
    ArrayBlockingQueue<Integer> buffer = new ArrayBlockingQueue<>(10);
    public Producer(ArrayBlockingQueue<Integer> buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        while (true) {
            synchronized(buffer) {
                while (buffer.remainingCapacity() == 0) {
                    try {
                        buffer.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            try {
                synchronized (buffer) {
                    buffer.add(1);
                    System.out.println("Producer active with remaining capacity: " + buffer.remainingCapacity());
                    buffer.notifyAll();
                }
            } catch (IllegalStateException ex) {
                ex.printStackTrace();
            }
        }
    }
}

版本2

public class Producer extends Thread {
    ArrayBlockingQueue<Integer> buffer = new ArrayBlockingQueue<>(10);

    public Producer(ArrayBlockingQueue<Integer> buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        while (true) {
            while (buffer.remainingCapacity() == 0) {
                synchronized (buffer) {
                    try {
                        buffer.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            try {
                synchronized (buffer) {
                    buffer.add(1);
                    System.out.println("Producer active with remaining capacity: " + buffer.remainingCapacity());
                    buffer.notifyAll();
                }
            } catch (IllegalStateException ex) {
                ex.printStackTrace();
            }
        }
    }
}

仅仅是为了它,这是该问题的另一个版本代码似乎正在起作用。我只是把它放在这里,以防它对其他人有帮助,或者是否有人可以告诉我它是否错误以及为什么。与上面的版本相比,这个版本在代码中有等待和notifyAll方法“翻转”的位置。

while(true) {
            while(buffer.remainingCapacity() == 0){
                synchronized (buffer){
                    buffer.notifyAll();
                }
            }
            try {
                buffer.add(1);
                System.out.println("Producer active with remaining capacity: " + buffer.remainingCapacity());
            } catch (IllegalStateException ex) {
                synchronized (buffer){
                    try {
                        buffer.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

2 个答案:

答案 0 :(得分:2)

同步考虑的事情是“什么操作需要原子化?”在您的示例中,check-then-wait必须是原子的。否则,请考虑以下事件序列:

  • 线程1:检查已执行但未通过
  • 线程2:更改缓冲区更改检查结果
  • 线程2:通知名称
  • 线程1:等待叫

在这种情况下,Thread1会错过通知并阻止通知,直到另一个通知到来。将检查放在同步块内消除了这种可能性,因为在Thread1调用wait之前,Thread2无法调用notify。

答案 1 :(得分:1)

首先,在该界面的documentation中显示了使用ArrayBlockingQueue或实际上任何BlockingQueue生产者/消费者问题的正确方法,稍微调整了您的示例:< / p>

生产者

    try {
        while (true) {
            queue.put(1);
        }
    } catch (InterruptedException ex) {
        // something want's us to stop.
    }

消费

    try {
        while (true) {
            Integer thing = queue.take();
        }
    } catch (InterruptedException ex) {
        // something want's us to stop.
    }

正好有synchronized / wait / notify,因为这些队列提供了puttake内置的内容。你应该在实践中使用它们。编写自己只会导致错误。 由于您手动使用synchronized / wait / notify,因此您可以将任何内容用作缓冲区。例如。 ArrayList。当然,这些方法没有remainingCapacity()方法,所以绑定队列毕竟不是一个糟糕的选择。

但是,除了死锁之外,你的实现还有另一个问题:如果有多个生产者或消费者,它将失败:你的代码每次迭代有2个同步块。现在可能发生的是:

  • 队列正好有1个广告位。
  • 制作人1,检查队列中是否有空格,离开同步
  • 制作人2,检查队列中是否有空格,离开同步
  • 制作人2,进入第二个同步,放入内容,队列现已满,离开
  • 制作人1,进入第二个同步,放东西.. arr - 崩溃。

你真正需要同步的是一个大的原子操作是整个check-then-wait-and-put的东西。你最终将大致

    try {
        while (true) {
            // produce outside of synchronized
            Integer product = 1;

            // BEGIN put atomic
            synchronized (buffer) {
                while (buffer.remainingCapacity() == 0) {
                    buffer.wait();
                }
                buffer.add(product);
                buffer.notifyAll();
                System.out.println("Producer active with remaining capacity: "
                        + buffer.remainingCapacity());
            }
            // END put atomic

            // consumer should likewise consume the product outside
            // of the synchronized block
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

BEGIN和END之间的部分大致也是put()ArrayBlockingQueue的实现。

PS:你的&#34;翻转&#34;例子也确实是错误的。除了那两个同步块: 当消费者处于缓慢状态时,请查看程序的CPU消耗:由于生产者主动停留在循环通知中,因此会出现峰值。这基本上是100%的CPU负载。

此外,使用IllegalStateException检测到您不应该添加内容,然后重试至少是糟糕的风格,我不确定是否还有其他隐藏的死锁当谈到多个生产者或消费者时。

最后,您依靠内置于buffer.add()的同步来确保您甚至可以获得该异常。在编写自己的p / c时,切勿在{{1​​}}之外读取或写入缓冲区。

您可能希望使用像synchronized这样的非同步集合来实现此功能,因此代码中的错误不会被其他内容同步隐藏。