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 unseenLa 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:
| Regla | Borde happens-before |
|---|---|
| Orden de programa | Cada acción en un hilo ocurre antes que las acciones posteriores en ese mismo hilo |
| Bloqueo de monitor | Desbloquear un monitor ocurre antes que un bloqueo posterior del mismo monitor |
| Volatile | Una escritura en un campo volatile ocurre antes que cada lectura posterior de él |
| Inicio de hilo | thread.start() ocurre antes que cualquier acción en el hilo iniciado |
| Unión de hilo | Todas las acciones en un hilo ocurren antes que otro hilo retorne de su join() |
| Campos final | Las 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 hereAtomicidad 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.
| Problema | Síntoma | Solución |
|---|---|---|
| Visibilidad | Un hilo nunca ve un valor actualizado | volatile, synchronized, final |
| Atomicidad | Actualizaciones perdidas por x++ bajo contención | synchronized, AtomicInteger, bloqueos |
| Reordenamiento | Las operaciones aparecen fuera de orden | happens-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.
Qué extraer de la ejecución:
reader saw data = 42prueba que la escrituravolatileenflagpublicó la escritura simple endata— se garantiza que el lector la vea gracias al borde happens-before.atomic counter = 800000 (expected 800000)muestra queAtomicInteger.incrementAndGet()no perdió ninguna actualización entre 8 hilos haciendo 100,000 incrementos cada uno — unint++simple imprimiría un número menor y no determinista.final config = prod:443demuestra la publicación segura: los camposfinaldeConfigson correctos sin ningúnvolatileni bloqueo.synchronized sum = 10000confirma 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 necesitavolatileni bloqueo.
Capítulos Relacionados
- Java
volatile— la palabra clave de visibilidad en profundidad. - Sincronización en Java — exclusión mutua y el bloqueo de monitor.
- Variables Atómicas — operaciones atómicas sin bloqueo.
- Palabra clave
finaly Clases Inmutables — publicación segura por construcción.