Un'operazione delicata nel mio laboratorio oggi è andata completamente storta. Un attuatore al microscopio elettronico ha superato il suo limite e dopo una catena di eventi ho perso $ 12 milioni di apparecchiature. Ho ristretto oltre 40K linee nel modulo difettoso a questo:
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);
}
}
Alcuni esempi dell'output che sto ricevendo:
$ 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
Dato che qui non esiste alcuna aritmetica in virgola mobile e sappiamo tutti che gli interi con segno si comportano bene in caso di overflow in Java, penso che non ci sia nulla di sbagliato in questo codice. Tuttavia, nonostante l'output indichi che il programma non ha raggiunto la condizione di uscita, ha raggiunto la condizione di uscita (è stato raggiunto e non raggiunto?). Perché?
Ho notato che ciò non accade in alcuni ambienti. Sono su OpenJDK 6 su Linux a 64 bit.
final
qualificatore (che non ha alcun effetto sul bytecode prodotto) nei campi x
e y
"risolve" il bug. Sebbene non influisca sul bytecode, i campi sono contrassegnati con esso, il che mi porta a pensare che questo sia un effetto collaterale di un'ottimizzazione JVM.
Point
p
costruito A che soddisfa p.x+1 == p.y
, quindi viene passato un riferimento al thread di polling. Alla fine il thread di polling decide di uscire perché pensa che la condizione non sia soddisfatta per uno dei messaggi Point
che riceve, ma poi l'output della console mostra che avrebbe dovuto essere soddisfatto. La mancanza di volatile
qui significa semplicemente che il thread di polling potrebbe bloccarsi, ma questo chiaramente non è il problema qui.
synchronized
non causa l'errore? Questo perché ho dovuto scrivere casualmente il codice fino a quando non ho trovato uno che riproducesse questo comportamento in modo deterministico.