QFixed get 和 put 方法中使用的习惯用法if (flag) wait
已损坏,您应该使用 while 循环。看有关受保护块的 Oracle 教程 http://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html.
一旦我更改了类的名称以删除“固定”,并替换if
with while
在Q类中,像这样:
class Q {
int n;
boolean valueset = false;
synchronized int get(){
while(!valueset){
try {
wait();
} catch (InterruptedException ex) {
System.out.println("interrupted");
}
}
System.out.println("Got: " +n);
valueset = false;
notify();
return n;
}
synchronized void put(int n){
while (valueset){
try {
wait();
} catch (InterruptedException ex) {
System.out.println("interrupted");
}
}
this.n = n;
valueset = true;
System.out.println("Put: "+n);
notify();
}
}
我得到的输出开始于
Put: 0
Got: 0
Put: 1
Got: 1
Put: 2
Got: 2
Put: 3
Got: 3
Put: 4
Got: 4
Put: 5
Got: 5
Put: 6
Got: 6
Put: 7
Got: 7
Put: 8
Got: 8
Put: 9
Got: 9
Put: 10
Got: 10
...
其中每个值都被放置和获取一次,这就是您想要的输出。
使用 while 循环是一件好事的原因有很多。
等待线程放弃监视器,一旦它醒来,它必须重新获取监视器,然后才能继续退出等待方法。这意味着其他线程可以获取监视器并可能更改同步保护的数据的状态。一旦线程重新获取了监视器,它就需要再次检查条件,然后才能知道它认为收到通知的条件是否确实发生了。否则,线程将根据过时的信息决定要做什么。
在涉及三个或更多竞争线程的示例中,这将是一个大问题。然而,对于这个特定的情况,我没有看到有问题的操作顺序。
使用 while 循环的另一个原因是,线程退出等待并不一定意味着发生了通知。根据对象的 javadoc#wait http://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#wait%28long%29:
线程还可以在没有被通知、中断或超时的情况下唤醒,即所谓的虚假唤醒。虽然这种情况在实践中很少发生,但应用程序必须通过测试应导致线程被唤醒的条件来防止这种情况,并在条件不满足时继续等待。换句话说,等待应该总是在循环中发生,如下所示:
synchronized (obj) {
while (<condition does not hold>)
obj.wait(timeout);
... // Perform action appropriate to condition
}
这是由于 JVM 实现中的竞争条件造成的;正如文档所说,这种情况应该很少发生。但这可能是问题的根源,在没有通知的情况下等待返回可能会产生像您所看到的那样的多次获取的情况。