CodeGym /Corsi /JAVA 25 SELF /Diagnostica e debug di programmi multithread

Diagnostica e debug di programmi multithread

JAVA 25 SELF
Livello 53, Lezione 4
Disponibile

1. Thread Dump e analisi dello stato dei thread

Thread Dump (dump dei thread) — è un’istantanea dello stato di tutti i thread dell’applicazione in un momento specifico. È come una foto di gruppo di tutti i vostri thread: chi sta facendo cosa, chi è bloccato, chi sta aspettando chi. Thread Dump — il principale strumento per trovare deadlock, livelock e altri misteriosi blocchi.

Come ottenere un Thread Dump?

Da terminale (jstack):

Se avete il PID del processo Java, eseguite:

jstack <PID>

Il comando stamperà in console lo stato di tutti i thread indicando in quale stato si trovano e quali monitor (lock) detengono.

Dall’IDE (IntelliJ IDEA):
Nel menu «Run» → «Show Running List» → selezionare il processo → «Thread Dump».

Con VisualVM o JConsole:
Aprite il processo, individuate la scheda «Threads» ed effettuate uno snapshot dello stato.

Esempio di Thread Dump

Frammento 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)

Qui si vede che il thread «Thread-1» è bloccato (BLOCKED), detiene un monitor ma ne attende un altro. Se vedete più thread di questo tipo, in cui uno detiene la risorsa A e attende la B, mentre un altro detiene la B e attende la A — è un deadlock classico.

Stati dei thread

Stato Descrizione
RUNNABLE Il thread è in esecuzione o pronto all’esecuzione
BLOCKED Attende di acquisire un monitor (lock)
WAITING Attende notify()/notifyAll() (ad es., dopo una chiamata a wait())
TIMED_WAITING Attende con timeout (ad es., sleep, wait(timeout))
TERMINATED Thread terminato

Importante: lo stato RUNNABLE non implica che il thread stia eseguendo proprio ora — è solo pronto a essere eseguito (lo scheduler della JVM potrebbe non avviarlo immediatamente).

Come capire se c’è un deadlock?

Nel dump ci sono più thread in stato BLOCKED, ognuno dei quali attende un monitor detenuto da un altro thread dello stesso insieme.

Alla fine del dump jstack di solito scrive:

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"

Se i thread restano a lungo in BLOCKED o WAITING — è un motivo per indagare.

2. Monitoraggio e profilazione dei thread

VisualVM
VisualVM — un’utility gratuita inclusa nella maggior parte dei JDK. Consente di collegarsi a un processo, vedere lo stato dei thread, effettuare un Thread Dump, osservare il carico CPU, i thread attivi e quelli «bloccati».

Scheda Threads: si vede quanti thread sono stati creati, i loro stati e la cronologia dell’attività.

Thread Dump: il pulsante «Thread Dump» effettua uno snapshot analogo a jstack.

Java Mission Control e Flight Recorder

Java Mission Control (JMC): uno strumento avanzato di analisi del funzionamento della JVM in tempo reale. Aiuta a investigare lock, tempi di esecuzione, allocazioni, latenze.

Java Flight Recorder (JFR): profiler integrato nella JVM che raccoglie eventi su thread, lock, pause ecc.

Esempio: monitoraggio delle lock

In VisualVM o JMC si può vedere che:

  • Il thread «A» è bloccato sull’oggetto X.
  • Il thread «B» detiene l’oggetto X ma attende l’oggetto Y.
  • Il thread «C» detiene l’oggetto Y ma attende l’oggetto X.

Questa è una classica lock circolare (deadlock).

Come usare questi strumenti nella pratica?

  • Avviate l’applicazione con l’opzione -XX:+FlightRecorder (o semplicemente usate JDK 11+).
  • Aprite JMC, collegatevi al processo, avviate una registrazione (start recording).
  • Analizzate le «hot spot», le lock di lunga durata e la contesa tra thread.

3. Logging e tracing

Nei programmi multithread il debug «a occhio» è doloroso. Registrate l’ingresso/uscita dalle sezioni critiche (blocchi synchronized), le operazioni sulle variabili condivise, le attese e i risvegli dei thread — così capirete chi e quando ha acquisito o rilasciato una risorsa.

Come fare logging?

  • Usate gli strumenti standard: java.util.logging, SLF4J, Log4j.
  • Registrate il nome del thread: Thread.currentThread().getName().
  • Registrate timestamp e identificatori dei thread.
  • Registrate gli eventi di acquisizione/rilascio delle lock.

Esempio di logging

synchronized(lock) {
    System.out.println(Thread.currentThread().getName() + " ha acquisito il lock");
    // sezione critica
    System.out.println(Thread.currentThread().getName() + " rilascia il lock");
}

Uso dei nomi dei thread

Date ai thread nomi significativi!

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

Esempio di tracing 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() + " ha iniziato a lavorare");
        synchronized (this) {
            logger.info(Thread.currentThread().getName() + " è entrato in synchronized");
            // ...
        }
        logger.info(Thread.currentThread().getName() + " ha finito il lavoro");
    }
}

4. Best practice di diagnostica

Minimizzate l’estensione delle lock

Mantenete le lock per il minor tempo possibile.

Esempio sbagliato:

synchronized(lock) {
    // I/O lungo
    // calcoli complessi
    // accesso al DB
    // ... e solo poi lavoro con i dati condivisi
}

Esempio corretto:

// fuori dal synchronized: I/O lungo, calcoli

synchronized(lock) {
    // solo lavoro con i dati condivisi
}

Usate i nomi dei thread

Nomi significativi dei thread fanno risparmiare tempo nell’analisi di dump e log.

Scrivete test per il multithreading

Usate JUnit + CountDownLatch per modellare scenari concorrenti.

CountDownLatch latch = new CountDownLatch(2);
Runnable task = () -> {
    // ...
    latch.countDown();
};
new Thread(task, "Worker-1").start();
new Thread(task, "Worker-2").start();
latch.await(); // attendiamo la terminazione di entrambi i thread

Usate try-finally con ReentrantLock

Lock lock = new ReentrantLock();
lock.lock();
try {
    // sezione critica
} finally {
    lock.unlock();
}

Così non vi dimenticherete di rilasciare la lock anche in caso di eccezione. Per evitare i deadlock usate tryLock() con timeout.

Documentate perché serve la sincronizzazione

I commenti «Qui serve synchronized, perché…» aiuteranno, col tempo, a ricordare l’intento.

5. Pratica: analisi di un deadlock in un programma di test

Esempio di codice 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 acquisito lockA");
                try { Thread.sleep(100); } catch (InterruptedException ignored) {}
                synchronized (lockB) {
                    System.out.println("Thread-1: ha acquisito lockB");
                }
            }
        }, "Thread-1");

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

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

Come individuare un deadlock

  1. Avviate il programma — si bloccherà.
  2. Acquisite un thread dump (jstack o tramite VisualVM).
  3. Trovate «Thread-1» e «Thread-2» — vedrete che ognuno detiene un lock e ne attende un altro.
  4. Alla fine del dump ci sarà la sezione «Found one Java-level deadlock».

Come risolvere

  • Acquisite le lock sempre nello stesso ordine.
  • Usate ReentrantLock con tryLock() e timeout: se non riuscite ad acquisire tutte le lock — rilasciate e riprovate.

6. Errori tipici nella diagnostica di programmi multithread

Errore n. 1: incapacità di leggere un thread dump. I developer alle prime armi si spaventano davanti al dump: «Che sono questi strani stack trace e stati?». In realtà basta conoscere gli stati principali e cercare BLOCKED/WAITING per semplificare l’analisi.

Errore n. 2: ignorare i nomi dei thread. Senza nomi significativi, leggere un dump è come cercare un ago in un pagliaio. Non siate pigri: scegliete i nomi!

Errore n. 3: blocchi synchronized troppo estesi. Se sincronizzate grandi porzioni di codice, i thread si bloccheranno più spesso tra loro — ciò si vede dalle frequenti occorrenze di BLOCKED nel dump.

Errore n. 4: confondere RUNNABLE con un thread realmente in esecuzione. RUNNABLE non significa che stia «correndo» sulla CPU. È lo scheduler della JVM a decidere chi eseguire.

Errore n. 5: non usare gli strumenti di monitoraggio. Molti non conoscono VisualVM, JMC, Flight Recorder e si arrangiano con println. Usate gli strumenti — semplificano molto la vita.

Errore n. 6: assenza di logging delle operazioni critiche. Senza log, capire chi ha acquisito/rilasciato una lock e quando è quasi impossibile.

Errore n. 7: tentare di individuare le data race «a occhio». Le race condition non si manifestano sempre e subito — usate test con CountDownLatch, provocate la concorrenza con Thread.yield() e analizzate lo stato delle variabili condivise.

1
Compito
JAVA 25 SELF, livello 53, lezione 4
Bloccato
Detective all'interno del programma: Determinare lo stato del thread corrente 🕵️‍♂️
Detective all'interno del programma: Determinare lo stato del thread corrente 🕵️‍♂️
1
Compito
JAVA 25 SELF, livello 53, lezione 4
Bloccato
Accesso alla Stanza Segreta: registrazione di entrata e uscita dalla sezione critica 🚪
Accesso alla Stanza Segreta: registrazione di entrata e uscita dalla sezione critica 🚪
1
Sondaggio/quiz
Problemi del multithreading, livello 53, lezione 4
Non disponibile
Problemi del multithreading
Problemi del multithreading
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION