W3docs

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 um Runnable mantém sua classe de negócio livre.
  • Lambdas transformam a forma Runnable em uma única linha. Criar uma subclasse de Thread exige uma classe nomeada para o mesmo código.
  • O Runnable que você passa também pode ser entregue a um ExecutorService posteriormente. A subclasse de Thread fica 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 finishes

Alguns erros comuns de iniciantes:

  • start() é o que cria a thread do sistema operacional. Chamar run() 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. Uma Thread é de uso único. Chamar start() uma segunda vez lança IllegalThreadStateException. Para executar a mesma tarefa novamente, crie uma nova Thread ou use um ExecutorService.
  • join() pode lançar InterruptedException. É uma chamada bloqueante. Se alguém chamar interrupt() na thread que está aguardando em join(), 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:

ConstrutorQuando 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." BLOCKED significa que está aguardando um lock intrínseco; WAITING/TIMED_WAITING significa que está pausada em wait(), 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 currentThread

Thread.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.

java— editable, runs on the server

O que observar na execução:

  • As transições de estado correspondem ao contrato. Antes de start(), ambas as threads eram NEW. Após start(), eram RUNNABLE (ou TERMINATED se o trabalho era pequeno e terminou antes da impressão). Após join(), ambas eram TERMINATED. Esse é o ciclo de vida que Thread.State descreve.
  • 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() era false depois porque start() nunca foi chamado. Se você está depurando "nada parece estar rodando em paralelo," verifique se escreveu start() ou run().
  • O loop de interrupção não usou Thread.sleep como 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 linha Thread.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 main retornou, 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.

Prática

Prática
Você chama `t.run()` (não `t.start()`) em uma `Thread` cujo `Runnable` imprime o nome da thread atual. O que ele imprime?
Você chama `t.run()` (não `t.start()`) em uma `Thread` cujo `Runnable` imprime o nome da thread atual. O que ele imprime?
Was this page helpful?