Modelo de Memória Java
O Java Memory Model — quais leituras e escritas são visíveis entre threads e como o happens-before funciona.
O Java Memory Model (JMM) é a parte da especificação da linguagem que define quando uma thread tem a garantia de ver as escritas de outra thread. É o conjunto de regras por trás de volatile, synchronized e final — e a razão pela qual código multithread correto tem a aparência que tem.
Este capítulo explica por que o modelo existe, como a relação happens-before une tudo isso e qual ferramenta usar quando você tem um problema de visibilidade, atomicidade ou reordenamento.
Por Que o Modelo de Memória Existe
No hardware moderno, o valor que uma thread "escreve" em um campo pode ficar em um registrador de CPU ou cache local do núcleo muito antes de chegar à memória principal, e o compilador tem liberdade para reordenar instruções independentes. Sem regras, uma thread poderia definir um campo enquanto outra thread nunca vê a mudança — ou a vê fora de ordem.
O JMM define uma única garantia que domina tudo isso: a relação happens-before. Se a ação A happens-before a ação B, então os efeitos de A são visíveis para B. Todo o resto — volatile, locks, final, inícios e joins de threads — é apenas uma forma de criar uma aresta happens-before.
// Without synchronization, this loop may NEVER terminate:
// the reader thread can cache 'running' forever and miss the write.
static boolean running = true; // plain field — no guarantee
void reader() { while (running) { /* spin */ } } // may hang
void stopper() { running = false; } // may go unseenA Palavra-chave volatile
Declarar um campo como volatile faz duas coisas: toda leitura vai à memória principal (visibilidade), e uma escrita volatile happens-before toda leitura volatile posterior do mesmo campo (ordenamento). Isso não torna operações compostas como count++ atômicas.
public class Worker {
private volatile boolean running = true; // visible across threads
public void run() {
while (running) { // always sees the latest value
doWork();
}
}
public void stop() {
running = false; // guaranteed visible to run()
}
}Use volatile para um único sinalizador ou referência que é lida por muitas threads e escrita por uma. Recorra a ele quando precisar de visibilidade, não de exclusão mútua. Veja Java volatile para um tratamento mais aprofundado.
Happens-Before: As Regras Fundamentais
Happens-before é o contrato com o qual você programa de fato. Estas arestas são as que você cria intencionalmente:
| Regra | Aresta happens-before |
|---|---|
| Ordem do programa | Cada ação em uma thread happens-before ações posteriores na mesma thread |
| Lock de monitor | Desbloquear um monitor happens-before um lock posterior do mesmo monitor |
| Volatile | Uma escrita em um campo volatile happens-before toda leitura posterior dele |
| Início de thread | thread.start() happens-before qualquer ação na thread iniciada |
| Join de thread | Todas as ações em uma thread happen-before outra thread retornar de seu join() |
| Campos final | As escritas do construtor em campos final happen-before o objeto ser publicado |
// synchronized creates a happens-before edge through the same lock:
synchronized (lock) { shared = compute(); } // unlock here ...
// ... happens-before another thread's:
synchronized (lock) { use(shared); } // ... lock hereAtomicidade vs. Visibilidade
São dois problemas diferentes e precisam de ferramentas diferentes. volatile corrige a visibilidade mas não a atomicidade; synchronized e as classes de java.util.concurrent.atomic corrigem ambos para a seção que cobrem.
| Problema | Sintoma | Solução |
|---|---|---|
| Visibilidade | Uma thread nunca vê um valor atualizado | volatile, synchronized, final |
| Atomicidade | Atualizações perdidas de x++ sob contenção | synchronized, AtomicInteger, locks |
| Reordenamento | Operações aparecem fora de ordem | happens-before pelas ferramentas acima |
import java.util.concurrent.atomic.AtomicLong;
public class Counter {
private final AtomicLong hits = new AtomicLong();
public void record() { hits.incrementAndGet(); } // atomic + visible
public long total() { return hits.get(); }
}Campos final e Publicação Segura
Um campo final definido no construtor é fixado quando o construtor retorna. Qualquer thread que veja um objeto corretamente construído (cuja referência não vazou do construtor) tem a garantia de ver os valores corretos de seus campos final — sem necessidade de volatile ou lock. É por isso que objetos imutáveis são intrinsecamente thread-safe.
public final class Point {
private final int x, y; // frozen at construction
public Point(int x, int y) { this.x = x; this.y = y; }
public int x() { return x; }
public int y() { return y; }
}
// Share a Point across threads freely: its final fields are safely published.Um Exemplo Autocontido
O exemplo executável abaixo usa apenas o JDK. Ele exercita quatro ferramentas do modelo de memória em um único programa: volatile para visibilidade entre threads, AtomicInteger para contagem sem perdas de atualização, campos final para publicação segura e synchronized para acumulação atômica.
O que aprender com a execução:
reader saw data = 42prova que a escritavolatileemflagpublicou a escrita simples emdata— o leitor tem a garantia de vê-la por causa da aresta happens-before.atomic counter = 800000 (expected 800000)mostra queAtomicInteger.incrementAndGet()não perdeu nenhuma atualização em 8 threads fazendo 100.000 incrementos cada — umint++simples imprimiria um número menor e não determinístico.final config = prod:443demonstra publicação segura: os camposfinaldeConfigestão corretos sem nenhumvolatileou lock.synchronized sum = 10000confirma que os quatro escritores (1000+2000+3000+4000) acumularam pelo mesmo monitor sem perdas de adições.- Cada linha de saída corresponde a um mecanismo happens-before diferente, mas eles se compõem em um único programa — as ferramentas do JMM são complementares, não intercambiáveis.
Escolhendo a Ferramenta Certa
Um guia rápido de decisão ao recorrer a um mecanismo do modelo de memória:
- Um escritor, muitos leitores de um sinalizador ou referência? Use
volatile. - Contadores, acumuladores ou compare-and-set em uma única variável? Use as classes atômicas — elas evitam sobrecarga de lock.
- Uma atualização de múltiplas etapas que deve ser tudo ou nada? Proteja com
synchronized(ou um lock explícito). - Compartilhando estado somente leitura? Torne-o imutável com campos
final; veja Classes imutáveis. Sem necessidade devolatileou lock.
Capítulos Relacionados
- Java
volatile— a palavra-chave de visibilidade em profundidade. - Java Synchronization — exclusão mútua e o lock de monitor.
- Atomic Variables — operações atômicas sem lock.
- Palavra-chave
finale Classes Imutáveis — publicação segura por construção.