Finalizarea și întreruperea unui thread
Exemplele de thread-uri anterioare prezentau thread-ul ca un set secvențial de operații. După executarea ultimei operații, thread-ul se finaliza. Cu toate acestea, deseori există o altă organizare a thread-ului sub forma unui ciclu infinit. De exemplu, un thread de server ascultă într-un ciclu infinit un anumit port pentru a primi date. Și în acest caz, putem prevedea un mecanism de încheiere a thread-ului.
Finalizarea unui thread
O modalitate comună de finalizare a unui thread constă în verificarea unei variabile logice. Dacă aceasta este, de exemplu, false, atunci thread-ul încheie ciclul infinit și își termină execuția.
Să definim următoarea clasă de thread:
class MyThread implements Runnable {
private boolean isActive;
void disable(){
isActive = false;
}
MyThread(){
isActive = true;
}
public void run(){
System.out.printf("%s started... \n", Thread.currentThread().getName());
int counter = 1; // contorul ciclurilor
while(isActive){
System.out.println("Loop " + counter++);
try{
Thread.sleep(400);
}
catch(InterruptedException e){
System.out.println("Thread has been interrupted");
}
}
System.out.printf("%s finished... \n", Thread.currentThread().getName());
}
}
Variabila isActive indică activitatea thread-ului. Cu ajutorul metodei disable() putem reseta starea acestei variabile.
Acum folosim această clasă:
public static void main(String[] args) {
System.out.println("Main thread started...");
MyThread myThread = new MyThread();
new Thread(myThread, "MyThread").start();
try{
Thread.sleep(1100);
myThread.disable();
Thread.sleep(1000);
}
catch(InterruptedException e){
System.out.println("Thread has been interrupted");
}
System.out.println("Main thread finished...");
}
Așadar, mai întâi este lansat thread-ul secundar: new Thread(myThread, "MyThread").start(). Apoi, thread-ul principal este oprit pentru 1100 milisecunde și se apelează metoda myThread.disable(), care schimbă în thread flag-ul isActive. Thread-ul secundar se încheie.
Metoda interrupt
O altă modalitate de a întrerupe sau finaliza un thread este prin metoda interrupt(). Apelul acestei metode setează un statut pentru thread, indicând că acesta a fost întrerupt. Metoda în sine returnează true dacă thread-ul poate fi întrerupt, altfel returnează false.
Cu toate acestea, apelul metodei nu finalizează thread-ul, ci doar setează statutul: de exemplu, metoda isInterrupted() din clasa Thread va returna valoarea true. Putem verifica valoarea returnată de această metodă și să realizăm anumite acțiuni. De exemplu:
class JThread extends Thread {
JThread(String name){
super(name);
}
public void run(){
System.out.printf("%s started... \n", Thread.currentThread().getName());
int counter = 1; // contorul ciclurilor
while(!isInterrupted()){
System.out.println("Loop " + counter++);
}
System.out.printf("%s finished... \n", Thread.currentThread().getName());
}
}
public class Program {
public static void main(String[] args) {
System.out.println("Main thread started...");
JThread t = new JThread("JThread");
t.start();
try{
Thread.sleep(150);
t.interrupt();
Thread.sleep(150);
}
catch(InterruptedException e){
System.out.println("Thread has been interrupted");
}
System.out.println("Main thread finished...");
}
}
În clasa care moștenește Thread, putem obține statutul thread-ului curent folosind metoda isInterrupted(). Și atâta timp cât această metodă returnează false, putem rula ciclul. După ce este apelată metoda interrupt, isInterrupted() va returna true, iar astfel ieșim din ciclu.
Posibilă ieșire în consolă:
Main thread started...
JThread started...
Loop 1
Loop 2
Loop 3
Loop 4
JThread finished...
Main thread finished...
Dacă funcționalitatea principală este inclusă într-o clasă care implementează interfața Runnable, putem verifica statutul thread-ului cu ajutorul metodei Thread.currentThread().isInterrupted():
class MyThread implements Runnable {
public void run(){
System.out.printf("%s started... \n", Thread.currentThread().getName());
int counter = 1; // contorul ciclurilor
while(!Thread.currentThread().isInterrupted()){
System.out.println("Loop " + counter++);
}
System.out.printf("%s finished... \n", Thread.currentThread().getName());
}
}
public class Program {
public static void main(String[] args) {
System.out.println("Main thread started...");
MyThread myThread = new MyThread();
Thread t = new Thread(myThread, "MyThread");
t.start();
try{
Thread.sleep(150);
t.interrupt();
Thread.sleep(150);
}
catch(InterruptedException e){
System.out.println("Thread has been interrupted");
}
System.out.println("Main thread finished...");
}
}
Cu toate acestea, atunci când obținem statutul unui thread prin metoda isInterrupted(), trebuie să ținem cont că, dacă gestionăm excepția InterruptedException în blocul catch al ciclului, statutul thread-ului este resetat automat și, după aceea, isInterrupted() va returna false.
De exemplu, adăugăm o întârziere în ciclul thread-ului cu ajutorul metodei sleep:
public void run(){
System.out.printf("%s started... \n", Thread.currentThread().getName());
int counter = 1; // contorul ciclurilor
while(!isInterrupted()){
System.out.println("Loop " + counter++);
try{
Thread.sleep(100);
}
catch(InterruptedException e){
System.out.println(getName() + " has been interrupted");
System.out.println(isInterrupted()); // false
interrupt(); // resetăm din nou starea
}
}
System.out.printf("%s finished... \n", Thread.currentThread().getName());
}
Când thread-ul apelează metoda interrupt, metoda sleep va genera o excepție InterruptedException, iar controlul va trece la blocul catch. Dar dacă verificăm statutul thread-ului, vom observa că metoda isInterrupted() returnează false.
Ca alternativă, în acest caz putem întrerupe din nou thread-ul curent, apelând din nou metoda interrupt(). Astfel, la următoarea iterație a ciclului while, metoda isInterrupted() va returna true, iar ieșirea din ciclu va avea loc.
Alternativ, putem ieși direct din ciclu folosind break în blocul catch:
while(!isInterrupted()){
System.out.println("Loop " + counter++);
try{
Thread.sleep(100);
}
catch(InterruptedException e){
System.out.println(getName() + " has been interrupted");
break; // ieșirea din ciclu
}
}
Dacă ciclul infinit este plasat într-o construcție try...catch, este suficient să gestionăm excepția InterruptedException:
public void run(){
System.out.printf("%s started... \n", Thread.currentThread().getName());
int counter = 1; // contorul ciclurilor
try{
while(!isInterrupted()){
System.out.println("Loop " + counter++);
Thread.sleep(100);
}
}
catch(InterruptedException e){
System.out.println(getName() + " has been interrupted");
}
System.out.printf("%s finished... \n", Thread.currentThread().getName());
}