Command Palette

Search for a command to run...

ES·EN

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.

Puntos clave

  • volatile garantiza visibilidad, no atomicidad. Usá AtomicInteger para operaciones compuestas como incrementAndGet().
  • La regla happens-before: la liberación de un monitor ocurre-antes de su próxima adquisición. La escritura volatile ocurre-antes de su próxima lectura.
  • Para contadores con alta contención, LongAdder supera significativamente a AtomicLong al distribuir actualizaciones entre celdas.

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(); }