Classe Thread em Java
Crie e controle threads em Java estendendo a classe Thread ou passando um Runnable — e as diferenças entre as duas abordagens.
java.lang.Thread é o objeto que você mantém como referência quando quer iniciar, nomear, aguardar, interromper ou consultar uma thread de execução. O capítulo anterior introduziu threads no nível conceitual; este é o tour pela API. Tudo em java.util.concurrent — executors, futures, threads virtuais — é construído sobre Thread, por isso vale conhecer a classe bruta, mesmo que você normalmente use os wrappers de nível mais alto em código de produção.
Duas formas de criar uma thread
Uma Thread é um Runnable embrulhado em um objeto de controle. Existem duas formas de fornecer o Runnable:
// 1. Pass a Runnable to the constructor (the modern, preferred form)
Thread a = new Thread(() -> System.out.println("hello from " + Thread.currentThread().getName()));
// 2. Extend Thread and override run()
class HelloThread extends Thread {
@Override public void run() {
System.out.println("hello from " + getName());
}
}
Thread b = new HelloThread();Ambas funcionam; ambas executam seu código em uma nova thread. A primeira forma é o que praticamente todo código moderno usa, por três razões:
- Uma classe só pode estender uma outra classe. Se você estende
Thread, não pode estender mais nada — e a parte do seu código que é o trabalho quase nunca tem um bom motivo para ser uma thread no sentido OO. Passar umRunnablemantém sua classe de negócio livre. - Lambdas transformam a forma
Runnableem uma única linha. Criar uma subclasse deThreadexige uma classe nomeada para o mesmo código. - O
Runnableque você passa também pode ser entregue a umExecutorServiceposteriormente. A subclasse deThreadfica presa para rodar em sua própria thread dedicada.
Estenda Thread apenas quando você genuinamente quiser adicionar estado ou métodos à própria thread (raro). Para tudo o mais, passe um Runnable.
Iniciando e aguardando
Os dois métodos que você usará em quase toda thread:
Thread t = new Thread(() -> doWork(), "worker");
t.start(); // schedule it; return immediately
t.join(); // block the caller until the thread finishesAlguns erros comuns de iniciantes:
start()é o que cria a thread do sistema operacional. Chamarrun()diretamente executa o corpo na thread atual, de forma síncrona — nenhuma nova thread é criada. Este é o bug de multithreading mais comum para iniciantes. Se você não vêstart(), nenhum paralelismo aconteceu.start()só pode ser chamado uma vez. UmaThreadé de uso único. Chamarstart()uma segunda vez lançaIllegalThreadStateException. Para executar a mesma tarefa novamente, crie uma novaThreadou use umExecutorService.join()pode lançarInterruptedException. É uma chamada bloqueante. Se alguém chamarinterrupt()na thread que está aguardando emjoin(), a espera termina com a exceção. Você deve tratá-la ou propagá-la.
join(millis) aguarda no máximo aquele número de milissegundos antes de retornar, independentemente de a thread ter terminado ou não. Use quando quiser dar ao worker uma chance limitada de terminar graciosamente antes de escalar.
Os construtores que importam
Thread tem muitos construtores; na prática, quatro importam:
| Construtor | Quando usar |
|---|---|
new Thread(Runnable) | O caso base. Worker anônimo. |
new Thread(Runnable, String name) | Quase sempre preferível — nomes aparecem em logs, profilers, thread dumps. |
new Thread(ThreadGroup, Runnable, String) | Quando você precisa de um grupo explícito (raro; grupos estão largamente obsoletos). |
new Thread(ThreadGroup, Runnable, String, long stackSize) | Quando a pilha padrão (~1 MB) não é suficiente — ex: recursão profunda ou pressão de memória. |
O construtor vazio new Thread() existe e executa um run() vazio, que não faz nada. Não há motivo para usá-lo.
Sempre nomeie suas threads. "worker-1", "http-3", "flush-loop" — qualquer que seja o papel. Um thread dump cheio de Thread-7, Thread-12, Thread-19 é um thread dump que você não consegue ler.
Propriedades em uma instância de Thread
Os poucos campos e getters que você realmente vai usar:
t.setName("scanner-2"); // any time before or after start()
String name = t.getName();
t.setDaemon(true); // BEFORE start(); else IllegalThreadStateException
boolean d = t.isDaemon();
t.setPriority(Thread.NORM_PRIORITY); // 1..10; mostly advisory, see chapter 6
int p = t.getPriority();
Thread.State s = t.getState(); // NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED
boolean alive = t.isAlive(); // true between start() and run() returning
long id = t.threadId(); // Java 19+; old name: getId()Dois deles importam mais:
setDaemon(true)decide se a thread mantém a JVM ativa. Veja o capítulo anterior — daemons morrem com o programa; não-daemons mantêm a JVM rodando até retornarem.getState()é o que você observa em um thread dump para diagnosticar "por que a thread está travada."BLOCKEDsignifica que está aguardando um lock intrínseco;WAITING/TIMED_WAITINGsignifica que está pausada emwait(),join(),sleep(),LockSupport.park(), etc.
Métodos estáticos de Thread
Alguns métodos estáticos que você chamará de dentro do worker:
Thread.currentThread(); // the thread that's executing this code
Thread.sleep(2000); // pause this thread for ~2000 ms
Thread.yield(); // hint to the scheduler "go ahead and run someone else"
Thread.interrupted(); // returns and CLEARS the interrupt flag of currentThreadThread.sleep é o mais comum; ele lança InterruptedException, portanto os chamadores devem tratá-la ou propagá-la. Thread.yield quase nunca é a ferramenta certa — é uma dica vaga que a JVM e o SO podem ignorar. Se você quer coordenar, use uma primitiva de sincronização real.
Thread.interrupted() retorna true se a thread atual foi interrompida, e limpa a flag. t.isInterrupted() (método de instância, em uma thread diferente) retorna a flag sem limpá-la. Confundi-los é uma fonte comum de interrupções perdidas.
Interrupção: como você pede a uma thread para parar
Não há t.stop() seguro (o método existe, mas está obsoleto desde a versão 1.1 porque deixa locks mantidos e estado corrompido). O protocolo de desligamento cooperativo é:
Thread worker = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
doOneUnitOfWork();
}
}, "worker");
worker.start();
// ... later, from somewhere else:
worker.interrupt();
worker.join();interrupt() define a flag de interrupção do worker. Espera-se que o worker verifique a flag em pontos seguros e saia. Se o worker estiver bloqueado em sleep, wait, join ou em muitas chamadas de java.nio, a chamada bloqueante lança InterruptedException imediatamente para que a thread possa reagir.
Se você capturar InterruptedException e não quiser propagá-la, a convenção é redefinir a flag para que chamadores acima na pilha ainda vejam a interrupção:
try { Thread.sleep(1000); }
catch (InterruptedException e) {
Thread.currentThread().interrupt(); // re-arm the flag
return; // and give up cooperatively
}Engolir uma interrupção sem rearmar a flag é um bug. A flag é como o resto do programa sabe que você foi solicitado a parar.
Um exemplo completo: o ciclo de vida completo em um programa
O programa abaixo cria dois workers de formas diferentes (Runnable, subclasse), observa suas transições de estado, aguarda com join e demonstra o protocolo de interrupção em um terceiro worker.
O que observar na execução:
- As transições de estado correspondem ao contrato. Antes de
start(), ambas as threads eramNEW. Apósstart(), eramRUNNABLE(ouTERMINATEDse o trabalho era pequeno e terminou antes da impressão). Apósjoin(), ambas eramTERMINATED. Esse é o ciclo de vida queThread.Statedescreve. - A linha "t3 ran on thread: main" é o bug para lembrar para sempre.
t3.run()executou o corpo — na thread chamadora, de forma síncrona. Nenhuma nova thread foi criada.t3.isAlive()erafalsedepois porquestart()nunca foi chamado. Se você está depurando "nada parece estar rodando em paralelo," verifique se escreveustart()ourun(). - O loop de interrupção não usou
Thread.sleepcomo sua espera principal — apenas verificou a flag de forma contínua, com um sleep curto ocasional para que a interrupção pudesse encerrar o sleep rapidamente. O contrato é o mesmo de qualquer forma:isInterrupted()é o que o worker verifica;interrupt()é o que o solicitante chama. - Rearmar a flag dentro do
catch(a linhaThread.currentThread().interrupt()) preservou o sinal para qualquer código mais acima na pilha de chamadas. Sem essa linha, uma interrupção capturada e ignorada desapareceria — o que é uma das formas mais fáceis de escrever uma thread que não desliga limpa. - O daemon no final estava prestes a dormir por 60 segundos; em vez disso, a JVM encerrou assim que
mainretornou, matando-o a meio do sleep. Threads daemon podem manter qualquer tipo de recurso — mas também podem ser encerradas a qualquer momento, por isso você não deve colocar trabalho que exige commit nelas.
O que vem a seguir
O próximo capítulo, Interface Runnable em Java, detalha o Runnable em si — o que ele realmente é, por que Callable e Future foram adicionados sobre ele, e como lambdas mudaram a ergonomia de passar trabalho para uma thread.