W3docs

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 unseen

A 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:

RegraAresta happens-before
Ordem do programaCada ação em uma thread happens-before ações posteriores na mesma thread
Lock de monitorDesbloquear um monitor happens-before um lock posterior do mesmo monitor
VolatileUma escrita em um campo volatile happens-before toda leitura posterior dele
Início de threadthread.start() happens-before qualquer ação na thread iniciada
Join de threadTodas as ações em uma thread happen-before outra thread retornar de seu join()
Campos finalAs 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 here

Atomicidade 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.

ProblemaSintomaSolução
VisibilidadeUma thread nunca vê um valor atualizadovolatile, synchronized, final
AtomicidadeAtualizações perdidas de x++ sob contençãosynchronized, AtomicInteger, locks
ReordenamentoOperações aparecem fora de ordemhappens-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.

java— editable, runs on the server

O que aprender com a execução:

  • reader saw data = 42 prova que a escrita volatile em flag publicou a escrita simples em data — o leitor tem a garantia de vê-la por causa da aresta happens-before.
  • atomic counter = 800000 (expected 800000) mostra que AtomicInteger.incrementAndGet() não perdeu nenhuma atualização em 8 threads fazendo 100.000 incrementos cada — um int++ simples imprimiria um número menor e não determinístico.
  • final config = prod:443 demonstra publicação segura: os campos final de Config estão corretos sem nenhum volatile ou lock.
  • synchronized sum = 10000 confirma 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 de volatile ou lock.

Capítulos Relacionados

Prática

Prática
Que garantia a declaração de um campo como 'volatile' fornece no Java Memory Model?
Que garantia a declaração de um campo como 'volatile' fornece no Java Memory Model?
Was this page helpful?