我正在解决消费者/生产者问题,让自己熟悉Java中的并发问题。我的问题可能与C / P问题本身相邻。在下面的代码片段中,如果我使用的是版本1,程序似乎运行正常,而在版本2中,我似乎在一段时间后会出现死锁。
我只会发布Producer类,因为不需要消费者类。
我的问题是:我知道在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();
}
}
}
}
答案 0 :(得分:2)
同步考虑的事情是“什么操作需要原子化?”在您的示例中,check-then-wait必须是原子的。否则,请考虑以下事件序列:
在这种情况下,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
,因为这些队列提供了put
和take
内置的内容。你应该在实践中使用它们。编写自己只会导致错误。
由于您手动使用synchronized
/ wait
/ notify
,因此您可以将任何内容用作缓冲区。例如。 ArrayList
。当然,这些方法没有remainingCapacity()
方法,所以绑定队列毕竟不是一个糟糕的选择。
但是,除了死锁之外,你的实现还有另一个问题:如果有多个生产者或消费者,它将失败:你的代码每次迭代有2个同步块。现在可能发生的是:
你真正需要同步的是一个大的原子操作是整个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
这样的非同步集合来实现此功能,因此代码中的错误不会被其他内容同步隐藏。