Nivel 2 · 25 min
Concurrencia
La concurrencia en Java es esencial para construir sistemas backend responsivos. Entender cómo interactúan los hilos, dónde surgen las condiciones de carrera y cómo el modelo de memoria de la JVM garantiza la visibilidad es fundamental para escribir código multi-hilo correcto.
Ciclo de vida del hilo y la relación happens-before
Un hilo transita por los estados: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING y TERMINATED. El Java Memory Model (JMM) define cuándo las escrituras de un hilo son visibles para otro a través de la relación happens-before. Sin ella, la JVM y la CPU pueden reordenar instrucciones para optimizar el rendimiento, generando bugs de visibilidad que aparecen solo bajo carga.
synchronized, volatile y clases Atomic
synchronized provee exclusión mutua (solo un hilo ejecuta el bloque protegido a la vez) y establece happens-before entre la liberación de un monitor y su próxima adquisición. volatile garantiza la visibilidad de escrituras entre hilos pero NO la atomicidad de operaciones compuestas. AtomicInteger y AtomicLong usan instrucciones CAS (compare-and-swap) del CPU para proveer operaciones compuestas atómicas sin exclusión mutua completa. LongAdder es mejor que AtomicLong para contadores con alta contención porque distribuye entre celdas para reducir colisiones CAS.
Condiciones de carrera y deadlocks
Una condición de carrera ocurre cuando la correctitud del programa depende del timing relativo de los hilos. Un deadlock ocurre cuando dos o más hilos se esperan mutuamente para liberar locks que mantienen, creando un ciclo. Las estrategias de prevención incluyen ordenar los locks (siempre adquirirlos en el mismo orden global), usar tryLock() con timeout, o preferir utilidades de concurrencia de alto nivel sobre bloques synchronized crudos.
Code example
// INSEGURO: condición de carrera en int plano
private int counter = 0;
public void increment() { counter++; } // no es atómico!
// SEGURO: AtomicInteger para operaciones compuestas atómicas
private AtomicInteger counter = new AtomicInteger(0);
public void increment() { counter.incrementAndGet(); }
// SEGURO: synchronized para proteger múltiples campos
private int x, y;
public synchronized void update(int nx, int ny) {
x = nx;
y = ny;
}
// MEJOR para alta contención: LongAdder
private LongAdder hits = new LongAdder();
public void recordHit() { hits.increment(); }