W3docs

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 immediately

start() é 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+ overload

Trê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 (declarar throws) ou capturá-la e reativar o flag com Thread.currentThread().interrupt().
  • Não libera locks. Uma thread dormindo mantém todos os locks que possuía antes. Se você chamar Thread.sleep dentro de um bloco synchronized, nenhuma outra thread entra no bloco enquanto você dorme. Isso é quase sempre um bug; use wait ou Condition.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 finished

join é 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 flag

O 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:

  1. Verificar Thread.currentThread().isInterrupted() entre etapas de longa duração.
  2. Em todo catch (InterruptedException e), propagar ou reativar o flag com Thread.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 else

Uma 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 InterruptedException

getName() 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 changes

Você 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.

java— editable, runs on the server

O que observar na execução:

  • A linha bad.run() imprimiu ran on: main. Nenhuma nova thread foi criada. bad.isAlive() era false depois porque start() 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, embora slow fosse dormir por 2000. isAlive() ainda era true. join(ms) é a espera com limite — útil quando você quer dar ao worker uma chance graciosa de terminar antes de escalar.
  • slow.interrupt() encerrou seu Thread.sleep imediatamente lançando InterruptedException dentro do worker. Esse é o contrato: chamadas bloqueantes interrompíveis reagem a interrupt() saindo com a exceção, que é como o cancelamento cooperativo funciona na prática.
  • O worker bookkeeper capturou InterruptedException e reativou o flag com Thread.currentThread().interrupt(). O isInterrupted() subsequente retornou true. 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 de start() — chamá-lo depois teria lançado IllegalThreadStateException. E quando main retornou, 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.

Prática

Prática
Você captura `InterruptedException` em um worker, mas não quer lançar fora do loop. O que deve fazer com o flag de interrupção?
Você captura `InterruptedException` em um worker, mas não quer lançar fora do loop. O que deve fazer com o flag de interrupção?
Was this page helpful?