Uma operação sensível no meu laboratório hoje deu completamente errado. Um atuador em um microscópio eletrônico ultrapassou seus limites e, após uma cadeia de eventos, perdi US $ 12 milhões em equipamentos. Eu reduzi mais de 40 mil linhas no módulo defeituoso para isso:
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);
}
}
Algumas amostras da saída que estou recebendo:
$ 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
Como não há aritmética de ponto flutuante aqui, e todos sabemos que números inteiros assinados se comportam bem no estouro de Java, acho que não há nada de errado com esse código. No entanto, apesar da saída indicar que o programa não atingiu a condição de saída, ele atingiu a condição de saída (foi atingido e não atingido?). Por quê?
Notei que isso não acontece em alguns ambientes. Estou no OpenJDK 6 no Linux de 64 bits.
final
qualificador (que não afeta o código de código produzido) aos campos x
e y
"resolver" o erro. Embora não afete o bytecode, os campos são sinalizados com ele, o que me leva a pensar que esse é um efeito colateral de uma otimização da JVM.
Point
p
é construído que satisfaz p.x+1 == p.y
, então uma referência é passada para o encadeamento de polling. Eventualmente, o encadeamento de polling decide sair porque acha que a condição não é atendida para um dos Point
s que recebe, mas a saída do console mostra que deveria ter sido atendida. A falta volatile
daqui simplesmente significa que o tópico de pesquisa pode ficar preso, mas esse claramente não é o problema aqui.
synchronized
faz com que o erro não aconteça? Isso porque eu tive que escrever código aleatoriamente até encontrar um que reproduzisse esse comportamento deterministicamente.