1. Stati principali di un thread in Java
In Java un thread non è semplicemente «avviato» o «fermato». Ha un intero ciclo di vita e, in ogni fase, si comporta in modo diverso. Comprendere queste fasi — è la chiave per scrivere applicazioni multithread stabili e per effettuare il debug di strani «blocchi» e «terminazioni inattese».
Quali stati esistono?
Java definisce i seguenti stati principali del thread (sono elencati nell’enumerazione Thread.State):
| Stato | Descrizione |
|---|---|
|
Il thread è stato creato ma non ancora avviato (new Thread(...), ma start() non è stato chiamato) |
|
Il thread è pronto per l’esecuzione oppure è in esecuzione in questo momento |
|
Il thread sta aspettando il rilascio del monitor (bloccato su un blocco synchronized) |
|
Il thread attende che un altro thread lo «risvegli» (ad esempio tramite Object.wait()) |
|
Il thread attende con timeout (ad esempio, Thread.sleep(1000), wait(1000), join(1000)) |
|
Il thread ha terminato l’esecuzione |
Curiosità:
Nei libri e negli articoli più vecchi potresti trovare altre denominazioni o schemi leggermente diversi. Ma da Java 5+ questi stati sono considerati lo standard.
Schema visivo del ciclo di vita del thread
stateDiagram-v2
[*] --> NEW
NEW --> RUNNABLE: start()
RUNNABLE --> BLOCKED: tentativo di entrare in synchronized, ma il monitor è occupato
BLOCKED --> RUNNABLE: il monitor è stato rilasciato
RUNNABLE --> WAITING: wait(), join()
RUNNABLE --> TIMED_WAITING: sleep(), wait(timeout), join(timeout)
WAITING --> RUNNABLE: notify()/notifyAll(), join() è terminato
TIMED_WAITING --> RUNNABLE: il timeout è scaduto / notify()/notifyAll()
RUNNABLE --> TERMINATED: run() è terminato
WAITING --> TERMINATED: run() è terminato (raramente)
TIMED_WAITING --> TERMINATED: run() è terminato (raramente)
2. Metodi di gestione del thread
Sospensione: Thread.sleep(long millis)
A volte un thread ha bisogno di «dormire», per non intralciare gli altri o per attendere un evento. Il metodo Thread.sleep(ms) porta il thread nello stato
TIMED_WAITING per il numero di millisecondi specificato.
System.out.println("Il thread si addormenta per 2 secondi...");
Thread.sleep(2000); // Dormiamo per 2 secondi
System.out.println("Il thread si è svegliato!");
- Al termine del sonno il thread torna nello stato
(pronto a lavorare).RUNNABLE - Se il thread viene interrotto durante il sonno, viene lanciata InterruptedException.
Attendere la fine di un altro thread: join()
Talvolta non basta avviare un thread: occorre aspettare che finisca il lavoro. Per questo in Java esiste il metodo join():
Thread t = new Thread(() -> {
System.out.println("Sto lavorando...");
try { Thread.sleep(1000); } catch (InterruptedException e) {}
System.out.println("Fatto!");
});
t.start();
System.out.println("Attendo il completamento del thread t...");
t.join(); // Il thread corrente (ad esempio main) attende t
System.out.println("Il thread t è terminato!");
Qui il thread principale inizia ad attendere non appena viene chiamato join(). Per tutto questo tempo si trova nello stato
WAITING, finché il thread
t non termina l’esecuzione. Esiste anche la variante con timeout —
join(long millis). In tal caso il thread attende per un tempo limitato e il suo stato sarà
TIMED_WAITING.
Interruzione del thread: interrupt()
Capita di dover chiedere educatamente a un thread di terminare il lavoro, ad esempio se l’utente ha premuto «Annulla». A questo serve il metodo interrupt():
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
// Lavoriamo...
}
System.out.println("Il thread è stato terminato tramite interruzione!");
});
t.start();
// ... dopo un po’:
t.interrupt(); // Segnale al thread: "è ora di fermarsi"
È importante capire che la chiamata a interrupt() non uccide il thread all’istante. Si limita a impostare un flag speciale. Il thread stesso deve controllare periodicamente questo flag tramite isInterrupted() e terminare da solo. Se in quel momento il thread si trova in stato di sonno (sleep()) o di attesa (wait()), non solo vedrà il flag, ma riceverà immediatamente l’eccezione InterruptedException. È così che in Java è organizzato il modo «corretto» di fermare i thread: il programma non li interrompe con la forza, ma li avvisa che è ora di terminare.
3. Esempi di transizioni tra stati
Vediamo con alcuni esempi come un thread «viaggia» tra i suoi stati.
Esempio 1: NEW
→ RUNNABLE
→ TERMINATED
NEWRUNNABLETERMINATEDThread t = new Thread(() -> System.out.println("Ciao!"));
System.out.println(t.getState()); // NEW
t.start();
System.out.println(t.getState()); // RUNNABLE (oppure TERMINATED, se il thread è molto veloce)
t.join();
System.out.println(t.getState()); // TERMINATED
Esempio 2: RUNNABLE
→ TIMED_WAITING
→ RUNNABLE
→ TERMINATED
RUNNABLETIMED_WAITINGRUNNABLETERMINATEDThread t = new Thread(() -> {
try {
System.out.println("Mi addormento...");
Thread.sleep(1000); // TIMED_WAITING
System.out.println("Mi sono svegliato!");
} catch (InterruptedException e) {
System.out.println("Thread interrotto!");
}
});
t.start();
Esempio 3: RUNNABLE
→ WAITING
con join()
RUNNABLEWAITINGThread t1 = new Thread(() -> {
try { Thread.sleep(500); } catch (InterruptedException ignored) {}
System.out.println("t1 completato");
});
Thread t2 = new Thread(() -> {
try {
t1.join(); // t2 aspetta t1, si trova in WAITING
System.out.println("t2 ha aspettato t1");
} catch (InterruptedException ignored) {}
});
t1.start();
t2.start();
Esempio 4: BLOCKED
BLOCKEDObject lock = new Object();
Thread t1 = new Thread(() -> {
synchronized (lock) {
try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
System.out.println("t1 è uscito dal blocco synchronized");
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) {
System.out.println("t2 è entrato in synchronized");
}
});
t1.start();
Thread.sleep(100); // Lasciamo che t1 acquisisca il lock
t2.start();
Thread.sleep(100); // Lasciamo che t2 provi a entrare in synchronized
System.out.println("Stato di t2: " + t2.getState()); // BLOCKED
4. Come conoscere lo stato di un thread? Metodi isAlive() e getState()
- isAlive() — restituisce true se il thread è avviato e non è ancora terminato (
restituisce già false).TERMINATED - getState() — restituisce lo stato corrente del thread (un valore dell’enumerazione Thread.State).
Thread t = new Thread(() -> {});
System.out.println(t.isAlive()); // false (NEW)
t.start();
System.out.println(t.isAlive()); // true (RUNNABLE/WAITING/...)
t.join();
System.out.println(t.isAlive()); // false (TERMINATED)
5. Perché non si può «uccidere» direttamente un thread e altri consigli pratici
Nessun metodo «kill»!
In Java non esiste un metodo che consenta di «uccidere» un thread a comando. Perché? Perché non è sicuro: se il thread detiene qualche risorsa (file, connessione, lock), la sua terminazione forzata può lasciare il sistema in uno stato non consistente.
Metodi obsoleti: stop(), suspend(), resume()
Nelle versioni antiche di Java esistevano i metodi stop(), suspend(), resume(). Oggi sono contrassegnati come @Deprecated, e il loro utilizzo è vivamente sconsigliato. Perché?
- stop() può uccidere il thread in qualsiasi momento, lasciando i dati in uno stato non consistente.
- suspend() può «congelare» un thread che detiene un lock, e allora l’intero programma si blocca.
- resume() talvolta non riesce a «scongelare» un thread se questo è già terminato.
Approccio moderno:
Il thread deve terminare correttamente da solo, reagendo al flag di interruzione (isInterrupted()) o ad altri segnali.
Buone pratiche
- Non chiamare metodi contrassegnati come obsoleti.
- Usa il flag di interruzione per fermare il thread (interrupt() e controllo di isInterrupted()).
- Monitora gli stati dei thread durante il debug — aiuta a individuare blocchi e deadlock.
- Non dimenticare join(), se devi attendere il completamento del lavoro di un thread.
6. Errori tipici nel lavoro con il ciclo di vita di un thread
Errore n. 1: avviare nuovamente un thread.
In Java un thread può essere avviato una sola volta. Se chiami start() una seconda volta — otterrai IllegalThreadStateException. Se devi ripetere un’attività — crea un nuovo oggetto Thread.
Thread t = new Thread(() -> {});
t.start();
t.start(); // Genererà un’eccezione!
Errore n. 2: confondere run() e start().
Chiamare direttamente run() non esegue il codice in un nuovo thread — viene eseguito nel thread corrente (ad esempio in main). Solo start() avvia davvero un nuovo thread.
Errore n. 3: non gestire InterruptedException.
Se un thread dorme o attende e viene interrotto, verrà lanciata InterruptedException. Se la ignori, il thread può «addormentarsi per sempre» o terminare in modo inatteso.
Errore n. 4: non verificare lo stato del thread.
A volte il programma si blocca perché un thread aspetta un altro che è già terminato o che non inizierà mai a lavorare. Usa getState() e isAlive() per la diagnosi.
Errore n. 5: utilizzare metodi obsoleti di gestione dei thread.
I metodi stop(), suspend(), resume() — sono dannosi. Non usarli, anche se la tentazione è quella di «sistemare tutto in fretta».
GO TO FULL VERSION