Finalização e Interrupção de Threads em Java
Nos exemplos anteriores, uma thread era uma sequência finita de operações. No entanto, muitos cenários exigem threads que operam em ciclos contínuos, como um serviço que verifica a cada minuto por novas tarefas ou um servidor que aguarda indefinidamente por conexões de rede. Nesses casos, é essencial implementar um mecanismo para finalizar a thread de forma controlada.
Finalização Controlada com uma Flag Booleana
Uma abordagem intuitiva para controlar o ciclo de vida de uma thread é usar uma variável booleana como um sinalizador (flag). Enquanto o sinalizador for true
, a thread continua seu trabalho. Quando uma outra thread o altera para false
, o laço é encerrado, e a thread finaliza sua execução de forma segura.
Vamos definir uma classe que implementa essa lógica:
class MyTask implements Runnable {
private volatile boolean isActive; // A keyword volatile garante visibilidade entre threads
void disable() {
isActive = false;
}
MyTask() {
isActive = true;
}
public void run() {
System.out.printf("%s started... \n", Thread.currentThread().getName());
int counter = 1;
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());
}
}
A variável isActive
indica se a thread deve continuar executando. O método disable()
permite que um código externo altere esse sinalizador.
Nota: Em cenários de concorrência real, é uma boa prática declarar essa flag como
volatile
para garantir que as alterações feitas por uma thread sejam imediatamente visíveis para as outras.
Agora, vamos utilizar essa classe:
public class Program {
public static void main(String[] args) {
System.out.println("Main thread started...");
MyTask task = new MyTask();
new Thread(task, "MyTaskThread").start();
try {
Thread.sleep(1100);
task.disable(); // Sinaliza para a thread parar
Thread.sleep(1000); // Aguarda um pouco para ver a mensagem de finalização
} catch (InterruptedException e) {
System.out.println("Thread has been interrupted");
}
System.out.println("Main thread finished...");
}
}
No método main
, a thread filha é iniciada. A main thread
então pausa por 1100 milissegundos e invoca task.disable()
. Na próxima iteração do laço while
, a condição isActive
será falsa, e a thread filha encerrará sua execução de forma limpa.
O Mecanismo de Interrupção Padrão: O Método interrupt()
Embora a flag booleana funcione, Java oferece um mecanismo padronizado e mais robusto para sinalizar a uma thread que ela deve parar: o método interrupt()
.
É fundamental entender que chamar interrupt()
não finaliza a thread à força. Em vez disso, ele apenas define um status interno na thread para indicar que uma interrupção foi solicitada. Cabe ao código da própria thread verificar cooperativamente esse sinal e agir de acordo, geralmente encerrando sua execução. O método isInterrupted()
é usado para essa verificação.
class JThread extends Thread {
JThread(String name) {
super(name);
}
public void run() {
System.out.printf("%s started... \n", Thread.currentThread().getName());
int counter = 1;
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(); // Solicita a interrupção da thread t
Thread.sleep(150);
} catch (InterruptedException e) {
System.out.println("Thread has been interrupted");
}
System.out.println("Main thread finished...");
}
}
No laço while
, a condição !isInterrupted()
verifica o sinalizador de interrupção. Quando t.interrupt()
é chamado na main thread
, esse sinalizador é ativado, fazendo com que isInterrupted()
passe a retornar true
, o que causa a saída do laço.
Se a lógica estiver em uma classe que implementa Runnable
, a verificação é feita através de Thread.currentThread().isInterrupted()
.
Lidando com InterruptedException
A complexidade do mecanismo de interrupção surge quando métodos que bloqueiam a execução, como Thread.sleep()
, wait()
ou join()
, são usados dentro do laço. Se uma thread é interrompida enquanto está "dormindo" (bloqueada em sleep
), ela não apenas acorda imediatamente, mas também lança uma InterruptedException
.
Ao capturar essa exceção, ocorre um detalhe crucial: o status de interrupção da thread é automaticamente resetado para false
. A lógica por trás disso é que, ao lançar a InterruptedException
, a thread já foi notificada da interrupção através da exceção, então o sinalizador não é mais necessário.
Isso significa que, se não for tratada corretamente, uma chamada subsequente a isInterrupted()
retornará false
, e o laço continuaria infinitamente.
public void run() {
System.out.printf("%s started... \n", Thread.currentThread().getName());
int counter = 1;
while (!isInterrupted()) {
System.out.println("Loop " + counter++);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println(getName() + " has been interrupted");
System.out.printf("Is thread interrupted? %s\n", isInterrupted()); // Imprime false
// Restaura o status de interrupção para que o laço while pare na próxima iteração
interrupt();
}
}
System.out.printf("%s finished... \n", Thread.currentThread().getName());
}
Como o status de interrupção foi limpo, uma boa prática é chamar interrupt()
novamente dentro do bloco catch
para restaurá-lo. Assim, na próxima verificação, o laço while
terminará corretamente.
Uma alternativa mais direta é sair do laço imediatamente no bloco catch
:
while (!isInterrupted()) {
System.out.println("Loop " + counter++);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println(getName() + " has been interrupted");
break; // Sai do laço imediatamente
}
}
Frequentemente, a abordagem mais limpa e segura é colocar todo o laço while
dentro de um único bloco try
. Dessa forma, quando a InterruptedException
for lançada por sleep()
, a execução do laço será interrompida e o controle passará para o bloco catch
, saindo do try
e, consequentemente, finalizando o método run
.
public void run() {
System.out.printf("%s started... \n", Thread.currentThread().getName());
int counter = 1;
try {
while (!isInterrupted()) {
System.out.println("Loop " + counter++);
Thread.sleep(100);
}
} catch (InterruptedException e) {
System.out.println(getName() + " has been interrupted while sleeping");
}
System.out.printf("%s finished... \n", Thread.currentThread().getName());
}
Resumo
- Para finalizar threads em laços, pode-se usar uma flag booleana (preferencialmente
volatile
). - O método
interrupt()
é o mecanismo padrão de Java para solicitar a interrupção de uma thread; ele não força a parada, apenas sinaliza. - A thread deve verificar cooperativamente seu estado de interrupção usando
isInterrupted()
. - Quando métodos como
sleep()
lançamInterruptedException
, o status de interrupção da thread é limpo. - Ao capturar
InterruptedException
, é crucial ou sair do laço (break
) ou restaurar o status de interrupção (interrupt()
).