Czuła operacja w moim laboratorium dzisiaj poszła całkowicie nie tak. Siłownik mikroskopu elektronowego przekroczył granicę, a po łańcuchu wydarzeń straciłem sprzęt o wartości 12 milionów dolarów. Zawęziłem ponad 40 000 linii w wadliwym module do tego:
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);
}
}
Niektóre próbki danych wyjściowych, które otrzymuję:
$ 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
Ponieważ nie ma tutaj żadnej arytmetyki zmiennoprzecinkowej i wszyscy wiemy, że liczby całkowite ze znakiem zachowują się dobrze w przypadku przepełnienia w Javie, sądzę, że nie ma nic złego w tym kodzie. Jednak pomimo danych wyjściowych wskazujących, że program nie osiągnął warunku wyjścia, osiągnął warunek wyjścia (został osiągnięty i nie został osiągnięty?). Czemu?
Zauważyłem, że tak się nie dzieje w niektórych środowiskach. Korzystam z OpenJDK 6 na 64-bitowym systemie Linux.
final
kwalifikatora (który nie ma wpływu na wygenerowany kod bajtowy) do pól x
i y
„rozwiązuje” błąd. Chociaż nie wpływa to na kod bajtowy, pola są z nim oznaczone, co prowadzi mnie do wniosku, że jest to efekt uboczny optymalizacji JVM.
Point
p
Konstrukcja A jest spełniająca p.x+1 == p.y
, a następnie do wątku odpytywania jest przekazywane odwołanie . W końcu wątek odpytywania decyduje się wyjść, ponieważ uważa, że warunek nie jest spełniony dla jednego z Point
otrzymywanych komunikatów, ale następnie dane wyjściowe konsoli wskazują, że powinien był zostać spełniony. Brak volatile
tutaj oznacza po prostu, że wątek wyborczy może utknąć, ale najwyraźniej nie jest to problemem.
synchronized
powoduje, że błąd się nie zdarza? To dlatego, że musiałem losowo pisać kod, dopóki nie znalazłem takiego, który odtworzyłby to zachowanie deterministycznie.