CodeGym /Cursos /JAVA 25 SELF /Diagnóstico y depuración de programas multihilo

Diagnóstico y depuración de programas multihilo

JAVA 25 SELF
Nivel 53 , Lección 4
Disponible

1. Thread Dump y análisis del estado de los hilos

Thread Dump (volcado de hilos) — es una instantánea del estado de todos los hilos de la aplicación en un momento determinado. Es como una foto de grupo de todos tus hilos: quién está haciendo qué, quién se ha atascado, quién está esperando a quién. Thread Dump — tu herramienta principal para buscar deadlock, livelock y otros cuelgues misteriosos.

¿Cómo obtener un Thread Dump?

Desde la terminal (jstack):

Si tienes el PID del proceso Java, ejecuta:

jstack <PID>

El comando mostrará en la consola el estado de todos los hilos indicando en qué estado está cada uno y qué monitores (bloqueos) mantiene.

Desde la IDE (IntelliJ IDEA):
En el menú «Run» → «Show Running List» → selecciona el proceso → «Thread Dump».

Con VisualVM o JConsole:
Abre el proceso, ve a la pestaña «Threads» y toma la instantánea del estado.

Ejemplo de Thread Dump

Fragmento del dump:

"Thread-1" #12 prio=5 os_prio=0 tid=0x000000001e0c7800 nid=0x1a48 waiting for monitor entry [0x000000001f00f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at com.example.DeadlockDemo.lambda$main$0(DeadlockDemo.java:25)
    - waiting to lock <0x00000000d6d6baf8> (a java.lang.Object)
    - locked <0x00000000d6d6bb08> (a java.lang.Object)

Aquí se ve que el hilo «Thread-1» está bloqueado (BLOCKED), mantiene un monitor pero espera otro. Si ves varios hilos así, que tienen el recurso A y esperan B, y otro hilo tiene B y espera A — es un deadlock clásico.

Estados de los hilos

Estado Descripción
RUNNABLE El hilo está ejecutándose o listo para ejecutarse
BLOCKED Espera adquirir un monitor (bloqueo)
WAITING Espera a notify()/notifyAll() (por ejemplo, provocado por wait())
TIMED_WAITING Espera con tiempo de espera (por ejemplo, sleep, wait(timeout))
TERMINATED El hilo ha terminado

Importante: el estado RUNNABLE no siempre significa que el hilo se esté ejecutando ahora mismo — solo está listo para ejecutarse (el planificador de la JVM puede no lanzarlo de inmediato).

¿Cómo saber si tienes un deadlock?

En el dump hay varios hilos en estado BLOCKED, cada uno esperando un monitor que mantiene otro hilo del mismo conjunto.

Al final del dump, jstack suele escribir:

Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x00000000d6d6baf8 (object 0x00000000d6d6baf8, a java.lang.Object),
  which is held by "Thread-2"
"Thread-2":
  waiting to lock monitor 0x00000000d6d6bb08 (object 0x00000000d6d6bb08, a java.lang.Object),
  which is held by "Thread-1"

Si los hilos permanecen mucho tiempo en BLOCKED o WAITING — es motivo para investigar.

2. Monitorización y perfilado de hilos

VisualVM
VisualVM — una utilidad gratuita incluida en la mayoría de los JDK. Permite conectarse al proceso, ver el estado de los hilos, hacer un Thread Dump, ver la carga de la CPU, hilos activos y «colgados».

Pestaña Threads: se ve cuántos hilos se han creado, sus estados y el historial de actividad.

Thread Dump: el botón «Thread Dump» toma una instantánea análoga a jstack.

Java Mission Control y Flight Recorder

Java Mission Control (JMC): herramienta avanzada para analizar en tiempo real el funcionamiento de la JVM. Ayuda a investigar bloqueos, tiempos de ejecución, asignaciones y latencias.

Java Flight Recorder (JFR): perfilador integrado de la JVM que recoge eventos sobre hilos, bloqueos, pausas, etc.

Ejemplo: monitorización de bloqueos

En VisualVM o JMC puedes ver que:

  • El hilo «A» está bloqueado en el objeto X.
  • El hilo «B» mantiene el objeto X, pero espera el objeto Y.
  • El hilo «C» mantiene el objeto Y, pero espera el objeto X.

Este es un interbloqueo circular clásico (deadlock).

¿Cómo usar estas herramientas en la práctica?

  • Ejecuta la aplicación con la opción -XX:+FlightRecorder (o simplemente usa JDK 11+).
  • Abre JMC, conéctate al proceso e inicia la grabación (start recording).
  • Analiza los «puntos calientes», los bloqueos prolongados y la competencia entre hilos.

3. Registro y trazado

En programas multihilo, depurar «a ojo» conduce al dolor. Registra la entrada/salida de las secciones críticas (bloques de synchronized), las operaciones con variables compartidas, las esperas y los despertares de hilos — así entenderás quién y cuándo adquirió o liberó un recurso.

¿Cómo registrar?

  • Usa las herramientas estándar: java.util.logging, SLF4J, Log4j.
  • Registra el nombre del hilo: Thread.currentThread().getName().
  • Registra la hora y los identificadores de hilo.
  • Registra los eventos de adquisición/liberación de bloqueos.

Ejemplo de registro

synchronized(lock) {
    System.out.println(Thread.currentThread().getName() + " ha adquirido lock");
    // sección crítica
    System.out.println(Thread.currentThread().getName() + " sale de lock");
}

Uso de nombres de hilos

¡Pon nombres significativos a los hilos!

Thread t = new Thread(runnable, "MyWorker-1");

Ejemplo de trazado con un logger

import java.util.logging.Logger;

public class Example {
    private static final Logger logger = Logger.getLogger(Example.class.getName());

    public void doWork() {
        logger.info(Thread.currentThread().getName() + " inició el trabajo");
        synchronized (this) {
            logger.info(Thread.currentThread().getName() + " entró en synchronized");
            // ...
        }
        logger.info(Thread.currentThread().getName() + " terminó el trabajo");
    }
}

4. Mejores prácticas de diagnóstico

Minimiza el ámbito de los bloqueos

Mantén los bloqueos el menor tiempo posible.

Mal ejemplo:

synchronized(lock) {
    // E/S prolongada
    // cálculos complejos
    // acceso a la base de datos
    // ... y solo entonces trabajo con datos compartidos
}

Buen ejemplo:

// fuera de synchronized: E/S prolongada, cálculos

synchronized(lock) {
    // solo trabajo con datos compartidos
}

Usa nombres de hilos

Los nombres significativos de los hilos ahorran tiempo al analizar dumps y registros.

Escribe pruebas para multihilo

Usa JUnit + CountDownLatch para modelar escenarios concurrentes.

CountDownLatch latch = new CountDownLatch(2);
Runnable task = () -> {
    // ...
    latch.countDown();
};
new Thread(task, "Worker-1").start();
new Thread(task, "Worker-2").start();
latch.await(); // esperamos a que terminen ambos hilos

Usa try-finally con ReentrantLock

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

Así no olvidarás liberar el bloqueo incluso si hay una excepción. Para evitar interbloqueos, usa tryLock() con tiempo de espera.

Documenta por qué se necesita la sincronización

Los comentarios «Aquí se necesita synchronized porque…» te ayudarán a entender el propósito con el tiempo.

5. Práctica: análisis de un deadlock en un programa de prueba

Ejemplo de código con deadlock

public class DeadlockDemo {
    private static final Object lockA = new Object();
    private static final Object lockB = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (lockA) {
                System.out.println("Thread-1: ha adquirido lockA");
                try { Thread.sleep(100); } catch (InterruptedException ignored) {}
                synchronized (lockB) {
                    System.out.println("Thread-1: ha adquirido lockB");
                }
            }
        }, "Thread-1");

        Thread t2 = new Thread(() -> {
            synchronized (lockB) {
                System.out.println("Thread-2: ha adquirido lockB");
                try { Thread.sleep(100); } catch (InterruptedException ignored) {}
                synchronized (lockA) {
                    System.out.println("Thread-2: ha adquirido lockA");
                }
            }
        }, "Thread-2");

        t1.start();
        t2.start();
    }
}

Cómo detectar un deadlock

  1. Ejecuta el programa — se quedará colgado.
  2. Obtén un thread dump (jstack o a través de VisualVM).
  3. Busca «Thread-1» y «Thread-2» — verás que cada uno mantiene un lock y espera el otro.
  4. Al final del dump habrá una sección «Found one Java-level deadlock».

Cómo solucionarlo

  • Adquiere los bloqueos siempre en el mismo orden.
  • Usa ReentrantLock con tryLock() y tiempo de espera: si no puedes adquirir todos los bloqueos, suéltalos e inténtalo de nuevo.

6. Errores típicos al diagnosticar programas multihilo

Error n.º 1: Incapacidad para leer un thread dump. Los desarrolladores principiantes se asustan del dump: «¿Qué significan estas pilas de llamadas y estados tan extraños?» En realidad, basta con conocer los estados principales y buscar BLOCKED/WAITING para facilitar el análisis.

Error n.º 2: Ignorar los nombres de los hilos. Sin nombres significativos, entender un dump es como buscar una aguja en un pajar. No seas perezoso: ¡pon nombres!

Error n.º 3: Bloques de synchronized demasiado grandes. Si sincronizas grandes trozos de código, los hilos se bloquearán entre sí con mayor frecuencia — se nota por los frecuentes BLOCKED en el dump.

Error n.º 4: Confundir RUNNABLE con un hilo realmente en ejecución. RUNNABLE no siempre «corre» en el procesador. El planificador de la JVM decide a quién ejecutar.

Error n.º 5: No usar herramientas de monitorización. Muchos no conocen VisualVM, JMC, Flight Recorder y sufren con println. Usa las herramientas — simplifican mucho la vida.

Error n.º 6: No registrar operaciones críticas. Sin registros, es casi imposible saber quién y cuándo adquirió/liberó un bloqueo.

Error n.º 7: Intentar detectar condiciones de carrera «a ojo». Las condiciones de carrera no siempre aparecen ni de inmediato — usa pruebas con CountDownLatch, provoca la competencia mediante Thread.yield() y analiza el estado de las variables compartidas.

1
Tarea
JAVA 25 SELF, nivel 53, lección 4
Bloqueada
Detective dentro del programa: Determinar el estado del hilo actual 🕵️‍♂️
Detective dentro del programa: Determinar el estado del hilo actual 🕵️‍♂️
1
Tarea
JAVA 25 SELF, nivel 53, lección 4
Bloqueada
Acceso a la Habitación Secreta: Registro de entrada y salida de la sección crítica 🚪
Acceso a la Habitación Secreta: Registro de entrada y salida de la sección crítica 🚪
1
Cuestionario/control
Problemas de concurrencia, nivel 53, lección 4
No disponible
Problemas de concurrencia
Problemas de concurrencia
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION