今天,我实验室中的一个敏感操作完全出错。电子显微镜上的执行器越过边界,经过一连串的事件,我损失了1200万美元的设备。我将故障模块中的40,000多行缩小为:
import java.util.*;
class A {
static Point currentPos = new Point(1,2);
static class Point {
int x;
int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
}
public static void main(String[] args) {
new Thread() {
void f(Point p) {
synchronized(this) {}
if (p.x+1 != p.y) {
System.out.println(p.x+" "+p.y);
System.exit(1);
}
}
@Override
public void run() {
while (currentPos == null);
while (true)
f(currentPos);
}
}.start();
while (true)
currentPos = new Point(currentPos.x+1, currentPos.y+1);
}
}
我得到的一些输出样本:
$ java A
145281 145282
$ java A
141373 141374
$ java A
49251 49252
$ java A
47007 47008
$ java A
47427 47428
$ java A
154800 154801
$ java A
34822 34823
$ java A
127271 127272
$ java A
63650 63651
由于这里没有任何浮点算法,而且我们都知道带符号的整数在Java中的溢出情况下表现良好,因此我认为这段代码没有错。但是,尽管输出表明程序未达到退出条件,但程序仍达到了退出条件(是否达到和未达到?)。为什么?
我注意到这在某些环境中不会发生。我在64位Linux 上使用OpenJDK 6。
final
限定符(对产生的字节码没有影响)添加到字段中x
并y
“解决”该错误。尽管它不影响字节码,但是用它标记了字段,这使我认为这是JVM优化的副作用。
Point
p
构建满足的A p.x+1 == p.y
,然后将引用传递给轮询线程。最终,轮询线程决定退出,因为它认为接收到的条件之一不满足该条件Point
,但是控制台输出显示它应该已经满足。缺少volatile
此处只是意味着轮询线程可能会卡住,但这显然不是问题所在。
synchronized
不会导致错误发生吗?那是因为我不得不随机编写代码,直到找到可以确定性地重现此行为的代码为止。