Métodos de Thread em Java
Principais métodos de controle de threads em Java — start, run, sleep, join, interrupt, yield, setDaemon — com as armadilhas que afetam o código real.
Thread possui muitos métodos, mas apenas alguns são utilizados no código real. Este capítulo percorre esses métodos — o que cada um faz, o que não faz, e os erros que parecem corretos até que estejam errados. Os capítulos anteriores os apresentaram de passagem; aqui eles são analisados individualmente.
Esta página aborda o ciclo de vida e o controle de threads: start vs run, sleep, join, o protocolo de cancelamento cooperativo com interrupt, além dos auxiliares menores (yield, currentThread, nomenclatura, status daemon, prioridade, holdsLock, onSpinWait). Para uma visão mais ampla de como uma thread percorre os estados NEW, RUNNABLE, BLOCKED, WAITING e TERMINATED, veja Java Thread Life Cycle.
start() vs. run()
O bug de multithreading mais comum:
Thread t = new Thread(() -> work(), "worker");
t.run(); // wrong: runs work() on the CURRENT thread
t.start(); // right: spawns a new OS thread, returns immediatelystart() é o único método que cria uma nova thread no sistema operacional. run() é o corpo do trabalho — chamá-lo diretamente é apenas uma invocação de método normal que retorna quando o trabalho termina. Se você não vê start(), não há paralelismo acontecendo.
start() também é de uso único. Após o retorno de run(), a thread está em TERMINATED e não pode ser reiniciada. Uma segunda chamada a start() lança IllegalThreadStateException.
Thread.sleep(ms)
A chamada estática que suspende a thread atual por pelo menos a duração especificada:
Thread.sleep(1500); // sleep 1.5 seconds
Thread.sleep(0, 250); // 250 nanoseconds; precision varies by OS
Thread.sleep(Duration.ofMillis(1500)); // Java 19+ overloadTrês coisas a saber:
- Lança
InterruptedException. Sleep é interrompível — é assim que um worker é informado para parar de dormir e encerrar. Você pode propagar a exceção (declararthrows) ou capturá-la e reativar o flag comThread.currentThread().interrupt(). - Não libera locks. Uma thread dormindo mantém todos os locks que possuía antes. Se você chamar
Thread.sleepdentro de um blocosynchronized, nenhuma outra thread entra no bloco enquanto você dorme. Isso é quase sempre um bug; usewaitouCondition.await(veja Inter-thread Communication) quando precisar liberar o lock. - O tempo é "no mínimo", não "exatamente". O SO pode acordar você um pouco mais tarde sob carga; nunca acorda antes do tempo.
t.join() e t.join(ms)
Aguarda outra thread terminar:
t.join(); // block until t terminates
t.join(2000); // block up to 2 seconds, then continue regardless
boolean done = t.join(Duration.ofSeconds(2));// Java 19+, returns whether it finishedjoin é a forma de compor trabalho paralelo em múltiplas etapas: criar algumas threads, deixá-las executar, fazer join em todas e ler seus resultados. join() retorna quando o run() da thread alvo retorna (seja normalmente ou por exceção). Também lança InterruptedException para que os chamadores possam ser interrompidos durante a espera.
Um detalhe sutil: join(0) significa "join sem timeout" (ou seja, esperar para sempre), não "join com timeout zero". Se você quiser uma chamada real de "desistir imediatamente", use t.isAlive().
t.interrupt() e o flag
O protocolo de cancelamento cooperativo, em três chamadas:
t.interrupt(); // set t's interrupt flag (and unblock sleep/wait/join/park)
t.isInterrupted(); // ask whether the flag is set (does NOT clear)
Thread.interrupted(); // static; ask current thread, and CLEAR the flagO flag é apenas um volatile boolean no objeto Thread. interrupt() o define. Se t está atualmente em sleep, wait, join ou LockSupport.park (ou muitas chamadas bloqueantes de java.nio), essa chamada bloqueante lança InterruptedException imediatamente. Caso contrário, o flag aguarda que o worker o perceba por conta própria.
Um worker que deseja ser interrompível tem duas responsabilidades:
- Verificar
Thread.currentThread().isInterrupted()entre etapas de longa duração. - Em todo
catch (InterruptedException e), propagar ou reativar o flag comThread.currentThread().interrupt()— nunca engolir silenciosamente.
while (!Thread.currentThread().isInterrupted()) {
try {
doOneUnit();
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // restore flag for the loop check
}
}Thread.yield() — quase nunca a ferramenta certa
Thread.yield(); // hint: please run someone elseUma dica não vinculante para o scheduler. O SO é livre para ignorá-la. Praticamente não existe código em produção que precise de yield — se você quer aguardar um evento, use wait, uma Condition ou um Semaphore. Recorra a yield apenas para microbenchmarks, frameworks de teste de deadlock ou quando estiver escrevendo a própria JVM.
Thread.currentThread()
O acessor estático para a thread em que o código chamador está sendo executado. Os dois usos que você verá:
String who = Thread.currentThread().getName();
Thread.currentThread().interrupt(); // re-arm the flag after catching InterruptedExceptiongetName() também é a maneira padrão de rotular linhas de log para distinguir threads na saída em produção.
getName / setName
Os nomes importam para depuração. O nome padrão (Thread-3) é inútil em um thread dump.
Thread t = new Thread(this::flush, "flush-loop"); // name at construction (preferred)
t.setName("flush-loop-2"); // rename later if a role changesVocê pode renomear a qualquer momento, mas o valor no momento do dump ou do log é o que o leitor verá. Sempre passe um nome para o construtor.
setDaemon(true)
Thread t = new Thread(this::poll, "metrics-poller");
t.setDaemon(true); // BEFORE start(); else IllegalThreadStateException
t.start();Threads daemon não mantêm a JVM ativa — quando a última thread não-daemon termina, a JVM encerra as daemons abruptamente. Use-as para tarefas de manutenção que devem morrer com o programa (timers, flushers de métricas, polling loops). Não as use para trabalhos cuja conclusão você realmente precisa.
setPriority(int)
t.setPriority(Thread.MAX_PRIORITY); // 10
t.setPriority(Thread.MIN_PRIORITY); // 1
t.setPriority(Thread.NORM_PRIORITY); // 5 (default)Principalmente orientativo. O próximo capítulo aborda prioridades em detalhes; por ora, o ponto principal: não confie nelas para correção — o SO decide o que elas significam.
Thread.holdsLock(obj)
Um auxiliar estático para depuração:
assert Thread.holdsLock(monitor) : "expected to be inside a synchronized block on monitor";Retorna true se a thread chamadora possui o monitor intrínseco de obj. Útil para afirmar que "este método só deve ser chamado de dentro de um bloco synchronized" sem pagar o custo de aquisição de lock no caminho feliz.
Thread.onSpinWait() — Java 9+
while (!done) {
Thread.onSpinWait(); // hint to the CPU: I'm spinning, slow down
}Uma dica ao nível da CPU que pausa pipelines e reduz o consumo de energia durante um loop de espera tight. É especificamente para o caso muito específico em que você está girando alguns microssegundos aguardando outra thread alterar um flag; não é uma chamada geral de "ceder a CPU". Para qualquer coisa mais longa, use LockSupport.park ou uma Condition.
Um exemplo trabalhado: a maioria deles em um só lugar
O programa abaixo usa start, join com timeout, interrupt, sleep, isInterrupted e setName juntos — os métodos que você realmente chamaria em produção.
O que observar na execução:
- A linha
bad.run()imprimiuran on: main. Nenhuma nova thread foi criada.bad.isAlive()erafalsedepois porquestart()nunca foi chamado. Todo programa de multithreading em algum momento tem esse bug; uma vez que você o comete, nunca mais o comete. - O
slow.join(300)retornou após cerca de 300 ms, emboraslowfosse dormir por 2000.isAlive()ainda eratrue.join(ms)é a espera com limite — útil quando você quer dar ao worker uma chance graciosa de terminar antes de escalar. slow.interrupt()encerrou seuThread.sleepimediatamente lançandoInterruptedExceptiondentro do worker. Esse é o contrato: chamadas bloqueantes interrompíveis reagem ainterrupt()saindo com a exceção, que é como o cancelamento cooperativo funciona na prática.- O worker
bookkeepercapturouInterruptedExceptione reativou o flag comThread.currentThread().interrupt(). OisInterrupted()subsequente retornoutrue. Sem essa reativação, o flag é perdido e qualquer código mais acima na pilha de chamadas pensa que nenhuma interrupção aconteceu. daemon.setDaemon(true)foi chamado antes destart()— chamá-lo depois teria lançadoIllegalThreadStateException. E quandomainretornou, o daemon foi encerrado no meio do sleep; a JVM saiu porque não restava nenhuma thread não-daemon. Esse é o trade-off do daemon: nunca bloqueia a saída da JVM, nunca tem garantia de conclusão.
O que vem a seguir
O próximo capítulo, Java Thread Priority, aborda o método setPriority em Thread, o que as prioridades realmente fazem em sistemas operacionais reais e por que você deve tratá-las como uma dica, não como uma garantia.