“Mas nunca deu problema na minha máquina.”
Provavelmente porque sua máquina é x86. Troca pra ARM e alguns bugs de concorrência que estavam “dormindo” aparecem.
A ideia central: x86 costuma ser mais “conservador” na prática, enquanto ARM permite mais reordenações. Se seu código depende de comportamento “bonzinho” do hardware, ele pode sobreviver em x86 e falhar em ARM.
O que o Java Memory Model (JMM) realmente garante
O JMM não fala “threads compartilham memória e pronto”. Ele define:
- visibilidade: quando o que uma thread escreveu passa a ser visto por outra
- ordem: quais reordenações são permitidas
- happens-before: a relação que cria garantias reais entre threads
Sem um happens-before entre duas ações, você não tem garantia de:
- ver o valor mais recente
- ver as coisas na ordem que você escreveu
- ver um objeto “pronto”
Isso é o motivo de muitos bugs “fantasma”.
O erro comum: confiar na ordem do código
Você escreve:
x = 42;
ready = true;
E imagina que outra thread, ao ver ready == true, vai necessariamente ver x == 42.
O JMM permite que, sem sincronização, outra thread observe:
ready == true
x == 0
Porque:
- x pode ficar em cache/registrador
- as escritas podem ser reordenadas
- a outra thread pode ler valores antigos
O clássico pesado: “objeto meio construído”
Esse é o bug mais traiçoeiro.
instance = new MyObject();
Isso parece uma operação, mas por baixo vira algo tipo:
alocar memória
inicializar campos (construtor)
publicar a referência (instance aponta pro objeto)
Sem sincronização, a JVM/CPU podem efetivamente permitir que a publicação (3) aconteça antes da inicialização (2) ser visível para outras threads.
Então uma segunda thread pode ver:
if (instance != null) {
instance.doSomething();
}
E instance não é null, mas o objeto pode estar com campos ainda em estado “default” (0/null). Em padrões como double-checked locking isso já deu dor real em produção.
Onde volatile entra (e por que resolve)
Se você declara:
private static volatile MyObject instance;
Você ganha duas coisas importantes:
- Visibilidade: leitura de volatile vê o valor mais recente (não “preso” em cache local).
- Ordem: volatile cria barreiras que impedem certas reordenações ao redor da variável.
Regra que importa:
Uma escrita em um volatile acontece-before de qualquer leitura posterior do mesmo volatile.
Na prática: se a thread B leu instance (volatile) como não-nulo, ela passa a ter garantia de enxergar as escritas que aconteceram antes da thread A publicar essa referência (incluindo os writes do construtor que “prepararam” o objeto).
“Como funciona por baixo dos panos”
Escrita em volatile
Quando a JVM compila uma escrita em volatile, ela precisa garantir que:
todas as escritas anteriores não fiquem “penduradas” e invisíveis
a publicação do valor não “passe na frente” de coisas anteriores
Isso é implementado inserindo memory fences/barriers ao redor do acesso (o tipo exato depende da arquitetura e do JIT).
Leitura de volatile
Quando a JVM compila uma leitura de volatile, ela precisa garantir que:
- você não leia um valor velho do cache/registrador
- leituras seguintes não sejam movidas “pra antes” dessa leitura
Também usa barreiras e instruções com semântica apropriada.
x86 vs ARM: por que a diferença aparece
x86 (modelo mais forte na prática)
Em x86, muitas reordenações são menos agressivas e a plataforma tende a “ajudar” sem você pedir. Não significa que o código está correto — significa que o bug pode não se manifestar.
Resultado: muita gente escreve código sem happens-before e “passa”.
ARM (modelo mais fraco)
ARM permite mais reordenação e exige sincronização explícita para garantir ordem/visibilidade. Se você não usou volatile/synchronized/locks/atomics, ARM tem mais chance de mostrar o bug.
Resultado: o mesmo programa “ok” em x86 pode falhar em ARM sob carga.
Regra prática (sem filosofia)
Se tem compartilhamento entre threads, escolha uma estratégia:
- volatile: bom para flags/estado simples e publicação segura de referência
synchronized/Lock: bom para invariantes e operações compostas
Atomic*: bom para operações atômicas sem lock (CAS), com custos/limites próprios
Se você não tem happens-before, você está apostando na arquitetura e no acaso.
Top comments (0)