以一个普通的生产者消费者问题作为例子:
//代码中的number 即为要消费和生产的产品
//生产者
public synchronized void increment() throws InterruptedException {
if(number!=0){
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"生产了"+number+"产品");
//生产产品后及时通知消费者来消费,否则死锁
notifyAll();
}
//消费者
public synchronized void decrement() throws InterruptedException {
if(number==0){
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"消费了"+number+"产品");
//消费产品后及时通知生产者开始生产,否则死锁
notifyAll();
}
上面代码,number作为产品,increment作为生产者,decrement作为消费者。
if(number==0)
判断是否需要生产或消费,number只能为1或者0,如果只有一个生产者,一个消费者,是没有问题的。
如果现在有A,B两个生产者,A在进行了一次if
判断后,进入了wait()
,之后B进行了一次if
判断后,也进入了wait()
然后消费者C调用了notifyAll()
,唤醒了A,B两个生产者,假设生产者A拿到锁,进行生产后number=1
并释放锁,此时由于多线程调度问题,消费者C没有来消费,但是生产者B却被调度到了,问题就发生在这里。
生产者B在执行wait()
之前已经进行了if()
判断,因此,生产者B会直接开始生产number=2
问题就这么发生了,生产者B是不应该被唤醒的,或者说,因为生产者A已经生产了,即使生产者B被唤醒,也不应该直接去生产,而是应该观察一下自己需要真的需要去生产。
如何解决?
只需要将if()
换做while()
即可,这样一来,生产者B即使被唤醒了,本身还处于while()
循环之中,如果不需要生产,那么会继续wait()
下去
仅为个人理解,如有不到之处恳请指正。
引用jdk1.8文档的一句话(java.lang.Object下的wait方法):
As in the one argument version, interrupts and spurious wakeups are
possible, and this method should always be used in a loop.
线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒 。 虽然这在实践中很少会发生,但应用程序必须通过测试应该使线程被唤醒的条件来防范,并且如果条件不满足则继续等待。 换句话说,等待应该总是出现在循环中。