Volver al inicio

Hilos en Java: arquitectura y sincronización

El artículo desglosa la estructura de hilos en Java desde el nivel del procesador de hardware hasta JVM. Describe mecanismos de sincronización, Race Condition, Deadlock, problemas de Livelock y CompletableFuture asíncrono para un desarrollo efectivo.

Multihilo en Java: desde núcleos de CPU hasta Deadlock
Advertisement 728x90

Arquitectura de hilos en Java y sincronización: del CPU a la JVM

El código Java se compila en bytecode, que la JVM convierte en instrucciones de máquina para el procesador. Cada núcleo de CPU ejecuta instrucciones de forma secuencial: una operación simple como un desplazamiento de bits en un registro ocurre en un ciclo de reloj. Los registros son celdas de memoria ultrarrápidas para almacenar números, direcciones y datos.

Los procesadores modernos usan arquitectura superscalar. Hyper-Threading permite que un solo hilo de hardware ejecute 2–6 instrucciones por ciclo, creando la ilusión de múltiples hilos lógicos por núcleo mediante cambios rápidos de contexto.

El número de hilos que se ejecutan simultáneamente está limitado por el número de núcleos. Los hilos comparten la memoria heap para objetos, mientras que cada uno tiene su propio stack para variables locales y referencias a objetos.

Google AdInline article slot

Condiciones de carrera y estructura de memoria

Una condición de carrera ocurre cuando varios hilos leen y modifican el mismo objeto en el heap al mismo tiempo. El resultado es impredecible, dependiendo del orden de las operaciones de lectura-modificación-escritura.

El código suele funcionar bien en local sin carga, pero en producción con multihilo, surgen colisiones de forma irregular. Un proceso del SO asigna memoria compartida; los hilos dentro de él son unidades de ejecución ligeras que compiten por recursos.

Mecanismos de sincronización: wait/notify y monitores

Cada objeto Java tiene un monitor (bloqueo) integrado. El bloque synchronized adquiere el monitor del objeto, garantizando:

Google AdInline article slot
  • Exclusión mutua: Solo un hilo ejecuta el código dentro del bloque.
  • Visibilidad: Los cambios son visibles para otros hilos al salir del bloque (con soporte de volatile).

El monitor incluye:

  • Conjunto de entrada: Cola de hilos esperando para adquirirlo.
  • Conjunto de espera: Hilos en estado de espera.

wait(), notify() y notifyAll() solo funcionan dentro de un bloque synchronized en el mismo objeto; de lo contrario, se lanza IllegalMonitorStateException.

  • wait(): Mueve el hilo al conjunto de espera.
  • notify(): Despierta un hilo aleatorio (riesgo de despertar perdido).
  • notifyAll(): Despierta todos los hilos (enfoque preferido).

Los métodos synchronized estáticos bloquean el monitor de la clase (Class), sin interferir con métodos de instancia (this).

Google AdInline article slot

Bloqueos avanzados: ReentrantLock y Semaphore

Para mayor control, usa java.util.concurrent.locks.ReentrantLock:

Lock lock = new ReentrantLock();
lock.lock();
try {
    // sección crítica
} finally {
    lock.unlock();
}

Métodos clave:

  • tryLock(): Adquiere sin esperar (devuelve false si falla).
  • tryLock(timeout, unit): Espera con tiempo límite.
  • lockInterruptibly(): Adquisición interrumpible.
  • new ReentrantLock(true): Bloqueo justo (orden FIFO, más lento).

Semaphore limita el acceso:

Semaphore sem = new Semaphore(3); // 3 permisos
sem.acquire(); // adquirir
sem.release(); // liberar

Problemas en multihilo: interbloqueo y vivihilo

Interbloqueo: Dependencias cíclicas de bloqueos. El hilo A tiene lock1 y espera lock2; el hilo B hace lo contrario. El sistema se congela.

Evítalo con:

  • Bloqueo en orden fijo.
  • Uso de tiempos límite.
  • Minimización de bloqueos anidados.

Vivihilo: Los hilos están ocupados pero no avanzan. Ejemplo: dos hilos ceden recursos cortésmente en un bucle, disparando la CPU sin progreso.

Síncrono vs asíncrono en hilos

Los métodos síncronos bloquean el hilo en E/S (p. ej., consulta a BD): el hilo espera inactivo la respuesta.

Asíncrono usa CompletableFuture, un contenedor para resultados diferidos (pendiente, completado o fallido).

Cadenas de operaciones:

  • thenApply(): Transforma el resultado.
  • thenCompose(): Compone con otro Future.
  • thenAccept(): Consume sin retorno.
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "result")
    .thenApply(s -> s.toUpperCase())
    .thenAccept(System.out::println);

Esto libera el hilo para otras tareas, aumentando el rendimiento en entornos multihilo.

Lecciones clave

  • Cada núcleo ejecuta un hilo de hardware; Hyper-Threading simula varios.
  • Las condiciones de carrera surgen del heap compartido; la sincronización garantiza exclusión mutua y visibilidad.
  • Prefiere notifyAll() sobre notify() para evitar despertares perdidos.
  • ReentrantLock amplía synchronized con tiempos límite, interrupciones y equidad.
  • CompletableFuture permite pipelines asíncronos sin bloquear hilos.

— Editorial Team

Advertisement 728x90

Leer después