Sospensione, Ripresa e Terminazione dei Thread in Java
- I thread in Java possono essere sospesi, ripresi e terminati in modo controllato.
- I metodi deprecati come
suspend()
,resume()
estop()
non dovrebbero essere utilizzati nei nuovi programmi Java. - Il controllo dei thread in Java 2 e versioni successive richiede l'uso di variabili di stato e meccanismi di sincronizzazione come
wait()
enotify()
. - I thread devono essere progettati per controllare il proprio stato di esecuzione.
- L'esempio fornito mostra come implementare la sospensione e la ripresa dei thread in modo sicuro e moderno.
Sospendere, Riprendere e Fermare i Thread
A volte, sospendere l'esecuzione di un thread è utile.
Per esempio, un thread separato può essere utilizzato per visualizzare l'ora del giorno. Se l'utente non vuole un orologio, allora il suo thread può essere sospeso. Qualunque sia il caso, sospendere un thread è una questione semplice. Una volta sospeso, riavviare il thread è anche una questione semplice.
I meccanismi per sospendere, fermare e riprendere i thread differiscono tra le prime versioni di Java, come Java 1.0, e le versioni più moderne, a partire da Java 2.
Controllo dei Thread in Java 1.0
Prima di Java 2, un programma utilizzava suspend()
, resume()
e stop()
, che sono metodi definiti da Thread
, per mettere in pausa, riavviare e fermare l'esecuzione di un thread. Anche se questi metodi sembrano essere un approccio perfettamente ragionevole e conveniente per gestire l'esecuzione dei thread, non devono essere utilizzati per i nuovi programmi Java.
La motivazione è la seguente. Il metodo suspend()
della classe Thread
è stato deprecato da Java 2 diversi anni fa. Questo è stato fatto perché suspend()
può a volte causare gravi guasti del sistema. Supponiamo che un thread abbia ottenuto dei lock su strutture dati critiche. Se quel thread viene sospeso a quel punto, quei lock non vengono rilasciati. Altri thread che potrebbero essere in attesa di quelle risorse possono rimanere bloccati in deadlock.
Il metodo resume()
è anche deprecato. Non causa problemi, ma non può essere utilizzato senza il metodo suspend()
come sua controparte.
Il metodo stop()
della classe Thread
, anch'esso, è stato deprecato da Java 2. Questo è stato fatto perché questo metodo può a volte causare gravi guasti del sistema. Supponiamo che un thread stia scrivendo su una struttura dati di importanza critica e abbia completato solo parte delle sue modifiche. Se quel thread viene fermato a quel punto, quella struttura dati potrebbe essere lasciata in uno stato corrotto. Il problema è che stop()
fa sì che qualsiasi lock che il thread chiamante detiene venga rilasciato. Quindi, i dati corrotti potrebbero essere utilizzati da un altro thread che sta aspettando sullo stesso lock.
Controllo dei Thread in Java 2 e Versioni Successive
Poiché ora non si possono utilizzare i metodi suspend()
, resume()
o stop()
per controllare un thread, si potrebbe pensare che non esista alcun modo per mettere in pausa, riavviare o terminare un thread.
Ma, fortunatamente, questo non è vero. Invece, un thread deve essere progettato in modo che il metodo run()
controlli periodicamente per determinare se quel thread dovrebbe sospendere, riprendere o fermare la propria esecuzione.
Tipicamente, questo viene realizzato introducendo una variabile flag che indica lo stato di esecuzione del thread. Finché questo flag è impostato su running, il metodo run()
deve continuare a far eseguire il thread. Se questa variabile è impostata su suspend, il thread deve mettersi in pausa. Se è impostata su stop, il thread deve terminare. Naturalmente, esistono vari modi per scrivere tale codice, ma il tema centrale sarà lo stesso per tutti i programmi.
Esempio
L'esempio seguente illustra come i metodi wait()
e notify()
che sono ereditati da Object
possono essere utilizzati per controllare l'esecuzione di un thread. Consideriamo il suo funzionamento.
La classe NuovoThread
contiene una variabile di istanza boolean
chiamata flagSospendi
, che viene utilizzata per controllare l'esecuzione del thread. Viene inizializzata a false
dal costruttore. Il metodo run()
contiene un blocco di istruzioni synchronized
che controlla flagSospendi
.
Se quella variabile è true
, il metodo wait()
viene invocato per sospendere l'esecuzione del thread. Il metodo ilMioSospendi()
imposta flagSospendi
a true
. Il metodo ilMioRiprendi()
imposta flagSospendi
a false
e invoca notify()
per risvegliare il thread. Infine, il metodo main()
è stato modificato per invocare i metodi ilMioSospendi()
e ilMioRiprendi()
.
// Sospendere e Riprendere i thread
// Approccio Moderno
class NuovoThread implements Runnable {
// Nome del Thread
String nome;
// Riferimento al Thread
Thread t;
// Flag per sospendere l'esecuzione
boolean flagSospendi;
// Costruttore per inizializzare il thread
NuovoThread(String nomeThread) {
nome = nomeThread;
t = new Thread(this, nome);
System.out.println("Nuovo thread: " + t);
flagSospendi = false;
}
// Questo è il punto di ingresso per il thread.
public void run() {
try {
for(int i = 15; i > 0; i--) {
System.out.println(nome + ": " + i);
Thread.sleep(200);
// Controlla se il thread deve essere sospeso
synchronized(this) {
while(flagSospendi) {
wait();
}
}
}
} catch (InterruptedException e) {
System.out.println(nome + " interrotto.");
}
// Stampa un messaggio quando il thread termina
System.out.println(nome + " in uscita.");
}
// Metodi per sospendere e riprendere il thread
synchronized void ilMioSospendi() {
flagSospendi = true;
}
synchronized void ilMioRiprendi() {
flagSospendi = false;
notify();
}
}
// Classe principale per eseguire il programma
class SospendiRiprendi {
// Punto di ingresso principale
public static void main(String[] args) {
// Crea due thread
NuovoThread ob1 = new NuovoThread("Uno");
NuovoThread ob2 = new NuovoThread("Due");
// Avvia i thread
ob1.t.start();
ob2.t.start();
// Sospende e riprende i thread
try {
Thread.sleep(1000);
ob1.ilMioSospendi();
System.out.println("Sospendo thread Uno");
Thread.sleep(1000);
ob1.ilMioRiprendi();
System.out.println("Riprendo thread Uno");
ob2.ilMioSospendi();
System.out.println("Sospendo thread Due");
Thread.sleep(1000);
ob2.ilMioRiprendi();
System.out.println("Riprendo thread Due");
} catch (InterruptedException e) {
System.out.println("Thread principale Interrotto");
}
// Attende che i thread finiscano
try {
System.out.println("In Attesa che i thread finiscano.");
ob1.t.join();
ob2.t.join();
} catch (InterruptedException e) {
System.out.println("Thread principale Interrotto");
}
// Stampa un messaggio quando il thread principale termina
System.out.println("Thread principale in uscita.");
}
}
In questo esempio, i metodi ilMioSospendi()
e ilMioRiprendi()
sono utilizzati per controllare l'esecuzione del thread. Quando il thread viene sospeso, entra in uno stato di attesa fino a quando non viene ripreso. Questo approccio evita i problemi associati ai metodi deprecati come suspend()
, resume()
e stop()
.
Quando si esegue il programma, si vedranno i thread sospendersi e riprendere. Più avanti, nelle prossime lezioni, vedremo più esempi che utilizzano il meccanismo moderno di controllo dei thread.
Anche se questo meccanismo potrebbe non apparire semplice da usare come il vecchio modo, tuttavia, è il modo richiesto per assicurare che non si verifichino errori di runtime.
È l'approccio che deve essere utilizzato per tutto il nuovo codice.