W3docs

Modelo de Memoria de Java

El Modelo de Memoria de Java: qué lecturas y escrituras son visibles entre hilos y cómo funciona happens-before.

El Modelo de Memoria de Java (JMM) es la parte de la especificación del lenguaje que define cuándo se garantiza que un hilo vea las escrituras de otro hilo. Es el reglamento detrás de volatile, synchronized y final — y la razón por la que el código multihilo correcto tiene el aspecto que tiene.

Este capítulo explica por qué existe el modelo, cómo la relación happens-before lo une todo, y qué herramienta usar cuando se tiene un problema de visibilidad, atomicidad o reordenamiento.

Por Qué Existe el Modelo de Memoria

En el hardware moderno, el valor que un hilo "escribe" en un campo puede permanecer en un registro de CPU o en una caché local del núcleo mucho antes de llegar a la memoria principal, y el compilador tiene libertad para reordenar instrucciones independientes. Sin reglas, un hilo podría establecer un campo mientras otro hilo nunca ve el cambio — o lo ve fuera de orden.

El JMM define una única garantía que domina todo esto: la relación happens-before. Si la acción A ocurre antes que la acción B (happens-before), los efectos de A son visibles para B. Todo lo demás — volatile, bloqueos, final, inicios y uniones de hilos — es solo una forma de crear un borde 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

La Palabra Clave volatile

Declarar un campo volatile hace dos cosas: cada lectura va a la memoria principal (visibilidad), y una escritura volatile ocurre antes que cualquier lectura volatile posterior del mismo campo (ordenamiento). No hace que operaciones compuestas como count++ sean 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()
    }
}

Usa volatile para una única bandera o una referencia que es leída por muchos hilos y escrita por uno. Recurre a él cuando necesitas visibilidad, no exclusión mutua. Consulta Java volatile para un tratamiento más profundo.

Happens-Before: Las Reglas Fundamentales

Happens-before es el contrato contra el que realmente programas. Estos bordes son los que creas intencionalmente:

ReglaBorde happens-before
Orden de programaCada acción en un hilo ocurre antes que las acciones posteriores en ese mismo hilo
Bloqueo de monitorDesbloquear un monitor ocurre antes que un bloqueo posterior del mismo monitor
VolatileUna escritura en un campo volatile ocurre antes que cada lectura posterior de él
Inicio de hilothread.start() ocurre antes que cualquier acción en el hilo iniciado
Unión de hiloTodas las acciones en un hilo ocurren antes que otro hilo retorne de su join()
Campos finalLas escrituras del constructor en campos final ocurren antes de que el objeto sea 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

Atomicidad vs. Visibilidad

Son dos problemas diferentes y necesitan herramientas diferentes. volatile corrige la visibilidad pero no la atomicidad; synchronized y las clases de java.util.concurrent.atomic corrigen ambas para la sección que cubren.

ProblemaSíntomaSolución
VisibilidadUn hilo nunca ve un valor actualizadovolatile, synchronized, final
AtomicidadActualizaciones perdidas por x++ bajo contenciónsynchronized, AtomicInteger, bloqueos
ReordenamientoLas operaciones aparecen fuera de ordenhappens-before mediante las herramientas anteriores
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 y Publicación Segura

Un campo final establecido en el constructor queda congelado en el momento en que el constructor retorna. Cualquier hilo que vea un objeto correctamente construido (uno cuya referencia no se filtró desde el constructor) tiene garantizado ver los valores correctos para sus campos final — sin necesidad de volatile ni bloqueo. Por eso los objetos inmutables son inherentemente seguros para hilos.

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.

Un Ejemplo Autónomo

El ejemplo ejecutable a continuación usa solo el JDK. Ejercita cuatro herramientas del modelo de memoria en un solo programa: volatile para visibilidad entre hilos, AtomicInteger para conteo sin pérdida de actualizaciones, campos final para publicación segura, y synchronized para acumulación atómica.

java— editable, runs on the server

Qué extraer de la ejecución:

  • reader saw data = 42 prueba que la escritura volatile en flag publicó la escritura simple en data — se garantiza que el lector la vea gracias al borde happens-before.
  • atomic counter = 800000 (expected 800000) muestra que AtomicInteger.incrementAndGet() no perdió ninguna actualización entre 8 hilos haciendo 100,000 incrementos cada uno — un int++ simple imprimiría un número menor y no determinista.
  • final config = prod:443 demuestra la publicación segura: los campos final de Config son correctos sin ningún volatile ni bloqueo.
  • synchronized sum = 10000 confirma que los cuatro escritores (1000+2000+3000+4000) acumularon a través del mismo monitor sin pérdida de adiciones.
  • Cada línea de salida corresponde a un mecanismo happens-before diferente, pero se componen en un solo programa — las herramientas del JMM son complementarias, no intercambiables.

Cómo Elegir la Herramienta Correcta

Una guía de decisión rápida cuando recurres a un mecanismo del modelo de memoria:

  • ¿Un escritor, muchos lectores de una bandera o referencia? Usa volatile.
  • ¿Contadores, acumuladores o compare-and-set en una sola variable? Usa las clases atómicas — evitan la sobrecarga de bloqueos.
  • ¿Una actualización de múltiples pasos que debe ser todo o nada? Protégela con synchronized (o un bloqueo explícito).
  • ¿Compartir estado de solo lectura? Hazlo inmutable con campos final; consulta Clases inmutables. No se necesita volatile ni bloqueo.

Capítulos Relacionados

Práctica

Práctica
¿Qué garantía proporciona declarar un campo 'volatile' en el Modelo de Memoria de Java?
¿Qué garantía proporciona declarar un campo 'volatile' en el Modelo de Memoria de Java?
Was this page helpful?