1. Estados principais da thread em Java
Em Java, uma thread não é apenas “iniciada” ou “parada”. Ela tem todo um ciclo de vida, e em cada etapa se comporta de maneira diferente. Entender essas etapas — a chave para escrever aplicações multithread estáveis e para depurar estranhos “travamentos” e “encerramentos inesperados”.
Quais são os estados?
O Java define os seguintes estados principais de thread (eles estão listados no enum Thread.State):
| Estado | Descrição |
|---|---|
|
A thread foi criada, mas ainda não foi iniciada (new Thread(...), mas start() não foi chamado) |
|
A thread está pronta para execução ou está executando agora |
|
A thread aguarda a liberação do monitor (bloqueada em um bloco synchronized) |
|
A thread aguarda até que outra thread a “acorde” (por exemplo, via Object.wait()) |
|
A thread aguarda com timeout (por exemplo, Thread.sleep(1000), wait(1000), join(1000)) |
|
A thread concluiu a execução |
Fato interessante:
Em livros e artigos antigos, você pode encontrar outros nomes ou esquemas um pouco diferentes. Mas a partir do Java 5+, esses estados são considerados o padrão.
Esquema visual do ciclo de vida da thread
stateDiagram-v2
[*] --> NEW
NEW --> RUNNABLE: start()
RUNNABLE --> BLOCKED: tentativa de entrar em synchronized, mas o monitor está ocupado
BLOCKED --> RUNNABLE: monitor liberado
RUNNABLE --> WAITING: wait(), join()
RUNNABLE --> TIMED_WAITING: sleep(), wait(timeout), join(timeout)
WAITING --> RUNNABLE: notify()/notifyAll(), join() finalizado
TIMED_WAITING --> RUNNABLE: timeout expirou / notify()/notifyAll()
RUNNABLE --> TERMINATED: run() terminou
WAITING --> TERMINATED: run() terminou (raramente)
TIMED_WAITING --> TERMINATED: run() terminou (raramente)
2. Métodos de controle de thread
Sono: Thread.sleep(long millis)
Às vezes, a thread precisa “dormir” para não atrapalhar outras ou para esperar um evento. O método Thread.sleep(ms) coloca a thread no estado
TIMED_WAITING pelo número de milissegundos especificado.
System.out.println("A thread vai dormir por 2 segundos...");
Thread.sleep(2000); // Dormimos por 2 segundos
System.out.println("A thread acordou!");
- Após o término do sono, a thread volta ao estado
(pronta para trabalhar).RUNNABLE - Se a thread for interrompida durante o sono, é lançada InterruptedException.
Aguardar o término de outra thread: join()
Às vezes não basta apenas iniciar uma thread; é preciso esperar ela terminar. Para isso, em Java existe o método join():
Thread t = new Thread(() -> {
System.out.println("Trabalhando...");
try { Thread.sleep(1000); } catch (InterruptedException e) {}
System.out.println("Pronto!");
});
t.start();
System.out.println("Aguardando a conclusão da thread t...");
t.join(); // A thread atual (por exemplo, main) espera t
System.out.println("A thread t foi concluída!");
Aqui, a thread principal começa a esperar assim que join() é chamado. Durante todo esse tempo ela fica no estado
WAITING, até que a thread
t termine a execução. Há também a variante com timeout —
join(long millis). Nesse caso, a thread espera por um tempo limitado e seu estado será
TIMED_WAITING.
Interrupção de thread: interrupt()
Às vezes é preciso pedir educadamente para a thread encerrar o trabalho, por exemplo, se o usuário clicou em “Cancelar”. Para isso, usa-se o método interrupt():
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
// Trabalhando...
}
System.out.println("Thread encerrada por interrupção!");
});
t.start();
// ... depois de um tempo:
t.interrupt(); // Sinal para a thread: "é hora de parar"
É importante entender que chamar interrupt() não mata a thread instantaneamente. Ele apenas define uma flag especial. A própria thread deve, de tempos em tempos, verificar essa flag por meio de isInterrupted() e encerrar-se sozinha. Se, nesse momento, a thread estiver dormindo (sleep()) ou aguardando (wait()), ela não apenas verá a flag, mas imediatamente receberá a exceção InterruptedException. É assim que funciona em Java a forma “correta” de parar threads: o programa não as interrompe à força, apenas notifica que é hora de encerrar.
3. Exemplos de transições entre estados
Vamos ver, com exemplos, como a thread “viaja” entre seus estados.
Exemplo 1: NEW
→ RUNNABLE
→ TERMINATED
NEWRUNNABLETERMINATEDThread t = new Thread(() -> System.out.println("Olá!"));
System.out.println(t.getState()); // NEW
t.start();
System.out.println(t.getState()); // RUNNABLE (ou TERMINATED, se a thread for muito rápida)
t.join();
System.out.println(t.getState()); // TERMINATED
Exemplo 2: RUNNABLE
→ TIMED_WAITING
→ RUNNABLE
→ TERMINATED
RUNNABLETIMED_WAITINGRUNNABLETERMINATEDThread t = new Thread(() -> {
try {
System.out.println("Vou dormir...");
Thread.sleep(1000); // TIMED_WAITING
System.out.println("Acordei!");
} catch (InterruptedException e) {
System.out.println("Thread interrompida!");
}
});
t.start();
Exemplo 3: RUNNABLE
→ WAITING
com join()
RUNNABLEWAITINGThread t1 = new Thread(() -> {
try { Thread.sleep(500); } catch (InterruptedException ignored) {}
System.out.println("t1 finalizada");
});
Thread t2 = new Thread(() -> {
try {
t1.join(); // t2 espera por t1, está em WAITING
System.out.println("t2 aguardou t1");
} catch (InterruptedException ignored) {}
});
t1.start();
t2.start();
Exemplo 4: BLOCKED
BLOCKEDObject lock = new Object();
Thread t1 = new Thread(() -> {
synchronized (lock) {
try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
System.out.println("t1 saiu do bloco synchronized");
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) {
System.out.println("t2 entrou em synchronized");
}
});
t1.start();
Thread.sleep(100); // Vamos permitir que t1 adquira o lock
t2.start();
Thread.sleep(100); // Vamos permitir que t2 tente entrar em synchronized
System.out.println("Estado de t2: " + t2.getState()); // BLOCKED
4. Como saber o estado da thread? Métodos isAlive() e getState()
- isAlive() — retorna true se a thread foi iniciada e ainda não terminou (
— já é false).TERMINATED - getState() — retorna o estado atual da thread (um valor do enum 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. Por que não é possível “matar” uma thread diretamente e outras dicas práticas
Não existe método “kill”!
Na linguagem Java não há um método que permita “matar” uma thread por comando. Por quê? Porque isso é inseguro: se a thread estiver segurando algum recurso (arquivo, conexão, bloqueio), encerrá-la à força pode deixar o sistema em estado inconsistente.
Métodos obsoletos: stop(), suspend(), resume()
Em versões antigas do Java existiam os métodos stop(), suspend(), resume(). Hoje eles estão marcados como @Deprecated, e seu uso é terminantemente desencorajado. Por quê?
- stop() pode matar a thread a qualquer momento, deixando os dados em estado inconsistente.
- suspend() pode “congelar” uma thread que detém um bloqueio, e então todo o programa trava.
- resume() às vezes não consegue “descongelar” a thread, caso ela já tenha terminado.
Abordagem moderna:
A thread deve encerrar-se corretamente por conta própria, reagindo à flag de interrupção (isInterrupted()) ou a outros sinais.
Boas práticas
- Não chame métodos marcados como obsoletos.
- Use a flag de interrupção para parar a thread (interrupt() e a verificação isInterrupted()).
- Observe os estados das threads durante a depuração — isso ajuda a encontrar travamentos e deadlocks.
- Não se esqueça de join() se precisar aguardar a conclusão do trabalho da thread.
6. Erros típicos ao trabalhar com o ciclo de vida da thread
Erro nº 1: iniciar a thread novamente.
Em Java, uma thread só pode ser iniciada uma única vez. Se você chamar start() pela segunda vez — receberá IllegalThreadStateException. Se precisar repetir a tarefa, crie um novo objeto Thread.
Thread t = new Thread(() -> {});
t.start();
t.start(); // Vai lançar uma exceção!
Erro nº 2: confundir run() e start().
Chamar run() diretamente não executa o código em uma nova thread — ele é executado na thread atual (por exemplo, em main). Somente start() realmente inicia uma nova thread.
Erro nº 3: não tratar InterruptedException.
Se a thread estiver dormindo ou aguardando e for interrompida, será lançada InterruptedException. Se ignorá-la, a thread pode “dormir para sempre” ou terminar inesperadamente.
Erro nº 4: não verificar o estado da thread.
Às vezes o programa trava porque uma thread espera por outra que já terminou ou nunca vai iniciar. Use getState() e isAlive() para diagnosticar.
Erro nº 5: usar métodos obsoletos de controle de thread.
Os métodos stop(), suspend(), resume() — são problemáticos. Não os use, mesmo que dê muita vontade de “consertar tudo rápido”.
GO TO FULL VERSION