Eine sensible Operation in meinem Labor ist heute völlig schief gelaufen. Ein Aktuator auf einem Elektronenmikroskop überschritt seine Grenzen, und nach einer Kette von Ereignissen verlor ich 12 Millionen Dollar an Ausrüstung. Ich habe über 40K Zeilen im fehlerhaften Modul auf diese eingegrenzt:
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);
}
}
Einige Beispiele der Ausgabe, die ich bekomme:
$ 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
Da es hier keine Gleitkomma-Arithmetik gibt und wir alle wissen, dass sich vorzeichenbehaftete Ganzzahlen beim Überlauf in Java gut verhalten, würde ich denken, dass an diesem Code nichts falsch ist. Obwohl die Ausgabe anzeigt, dass das Programm die Beendigungsbedingung nicht erreicht hat, hat es die Beendigungsbedingung erreicht (es wurde sowohl erreicht als auch nicht erreicht?). Warum?
Ich habe festgestellt, dass dies in einigen Umgebungen nicht der Fall ist. Ich bin auf OpenJDK 6 unter 64-Bit-Linux.
final
Qualifikators (der keinen Einfluss auf den erzeugten Bytecode hat) zu den Feldern x
und y
"Beheben" des Fehlers. Obwohl dies keinen Einfluss auf den Bytecode hat, sind die Felder damit gekennzeichnet, was mich zu der Annahme veranlasst, dass dies ein Nebeneffekt einer JVM-Optimierung ist.
Point
p
wird konstruiert, was erfüllt p.x+1 == p.y
, dann wird eine Referenz an den Abfragethread übergeben. Schließlich entscheidet sich der Abfragethread zum Beenden, weil er der Meinung ist, dass die Bedingung für eines der Point
empfangenen s nicht erfüllt ist , aber dann zeigt die Konsolenausgabe, dass sie erfüllt sein sollte. Das Fehlen von volatile
hier bedeutet einfach, dass der Polling-Thread stecken bleiben kann, aber das ist hier eindeutig nicht das Problem.
synchronized
dass der Fehler nicht auftritt? Das liegt daran, dass ich zufällig Code schreiben musste, bis ich einen fand, der dieses Verhalten deterministisch reproduziert.