W3docs

Ciclo de Vida de Threads Java

Os estados de uma thread Java — NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED — e como eles mudam.

Uma thread Java não tem muitos estados — são seis, todos eles valores do enum Thread.State. Mas esses seis são o vocabulário de dumps de threads, profilers e de toda investigação do tipo "por que meu programa travou" que você já fará. Saber o que cada estado significa e quais transições são possíveis é o que transforma um dump de thread de uma parede de stack traces em um diagnóstico.

Os seis estados

// java.lang.Thread.State — the six possible thread states
public enum State {
  NEW,                  // created, never started
  RUNNABLE,             // started; running or ready to run on a CPU
  BLOCKED,              // waiting for a monitor lock to enter a synchronized block
  WAITING,              // parked indefinitely (Object.wait, Thread.join, LockSupport.park)
  TIMED_WAITING,        // parked with a timeout (sleep, wait(ms), join(ms), park(nanos))
  TERMINATED            // run() has returned
}

As transições permitidas entre eles formam um ciclo de vida simples:

   NEW
    |  start()
    v
RUNNABLE  <----------+--------+-------+
    |   |            |        |       |
    |   | enters     | wakes  | timeout
    |   v sync       | from   | expires
    |  BLOCKED       | wait/  |
    |   |            | join   |
    |   | acquires   |        |
    |   v lock       |        |
    +-> RUNNABLE     |        |
    |                |        |
    | wait/join/park |        |
    v                |        |
WAITING -------------+        |
    |                         |
    | wait(ms)/join(ms)/sleep |
    v                         |
TIMED_WAITING ----------------+
    |
    | run() returns
    v
TERMINATED

Cada estado corresponde a algo visível em um dump de thread. Vamos percorrê-los.

NEW

Uma Thread que você construiu mas nunca chamou start(). Nenhum recurso de SO foi alocado; nada está em execução. As únicas transições possíveis são:

  • start()RUNNABLE
  • A thread é coletada pelo garbage collector sem nunca ter sido executada

Você pode chamar start() exatamente uma vez. Uma segunda chamada lança IllegalThreadStateException.

RUNNABLE

"A thread está viva e está atualmente em execução em uma CPU ou pronta para executar." O Java colapsa os estados "running" e "runnable" do SO em um único estado — não há como saber apenas pelo Thread.State se a thread está atualmente consumindo CPU. O escalonador do SO decide quais threads RUNNABLE realmente obtêm um núcleo a cada momento.

Uma thread RUNNABLE também é o estado em que uma thread se encontra quando está bloqueada em I/O (InputStream.read, Socket.read, FileChannel.read). Isso surpreende as pessoas: a thread está "pronta para executar" apenas no sentido de que nada na JVM a está bloqueando. O SO sabe que a thread está aguardando o disco; a JVM não sabe, então reporta RUNNABLE. Se você vir um dump de thread onde uma thread é RUNNABLE e seu frame do topo é socketRead0 ou similar, a thread está bloqueada em uma syscall — não consumindo CPU.

Aviso

RUNNABLE não significa "ocupada." É o estado mais mal interpretado em um dump de thread: uma thread estacionada dentro de uma chamada de I/O bloqueante (socketRead0, FileInputStream.read) reporta RUNNABLE mesmo que esteja usando zero CPU. Não conclua que uma thread está ativa com base no seu estado — leia seu frame de topo do stack, ou faça amostragem com um profiler.

BLOCKED

A thread está na porta de um bloco synchronized aguardando o lock do monitor. Alguma outra thread o possui; esta entrou na fila. Assim que o detentor liberar, um dos esperadores ganha o lock e faz a transição de volta para RUNNABLE.

BLOCKED é específico para synchronized — o mecanismo de lock intrínseco que a JVM incorpora. Código aguardando em um ReentrantLock não mostra BLOCKED; mostra WAITING (porque ReentrantLock é implementado em cima de LockSupport.park). Essa é uma distinção pequena, mas importante ao ler dumps.

A assinatura clássica de dump de thread para BLOCKED:

"worker-3" #19 prio=5 ... waiting for monitor entry
   java.lang.Thread.State: BLOCKED (on object monitor)
   at com.acme.Cache.put(Cache.java:42)
   - waiting to lock <0x000000076ab8e220> (a java.util.HashMap)
   at com.acme.Cache.miss(Cache.java:67)

Duas informações: em qual monitor você está aguardando (<0x000000076ab8e220>) e qual método está na porta. Pesquise no mesmo dump por - locked <0x000000076ab8e220> e você terá encontrado a thread que o possui.

WAITING

A thread escolheu aguardar indefinidamente. Três coisas colocam uma thread aqui:

  • Object.wait() — libera o monitor e estaciona até que alguém chame notify/notifyAll.
  • Thread.join() — sem timeout, estaciona até que a thread alvo termine.
  • LockSupport.park() — o primitivo sobre o qual ReentrantLock.lock(), await(), BlockingQueue.take(), e todo o java.util.concurrent são construídos.

Uma thread WAITING usa essencialmente nenhum recurso além de sua stack. Ela não fará transição até que alguém mais faça algo — um notify, o término de um alvo de join, um LockSupport.unpark. Se nada nunca a acionar, ela fica lá para sempre. É assim que deadlocks silenciosos parecem em um dump: duas threads, ambas em WAITING, ambas segurando o que a outra quer.

TIMED_WAITING

A mesma ideia que WAITING, mas com um prazo. A thread se acordará quando o timeout expirar, mesmo que nada mais aconteça. Coisas que produzem TIMED_WAITING:

  • Thread.sleep(ms)
  • Object.wait(ms)
  • Thread.join(ms)
  • LockSupport.parkNanos(...), LockSupport.parkUntil(...)
  • BlockingQueue.poll(timeout, unit), Future.get(timeout, unit), etc.

Se uma thread está confiavelmente presa em TIMED_WAITING pela duração que você especificou, isso não é um bug. Se ela permanecer lá além do timeout, ela foi reestacionada — alguém chamou wait(1000) em um loop ou a fila ainda está vazia.

TERMINATED

run() retornou (normalmente ou por exceção). A thread terminou; não pode ser reiniciada. t.isAlive() retorna false. Você ainda pode ler seu nome e ID para fins de log/depuração, mas a thread em si está finalizada.

Lendo o estado no seu próprio código

Thread.State é consultável publicamente, mas o valor é um snapshot — ele pode mudar entre a chamada e o uso que você fizer dele. Em produção, você quase nunca ramifica nele; você o usa para logging e diagnóstico. A JVM também expõe ThreadMXBean para dumps completos de threads, que é o que a maioria dos dashboards JMX exibe.

Thread t = new Thread(() -> doWork(), "worker");
System.out.println(t.getState());          // NEW
t.start();
System.out.println(t.getState());          // RUNNABLE (or TIMED_WAITING/BLOCKED/etc., racy)
t.join();
System.out.println(t.getState());          // TERMINATED

Um exemplo trabalhado: observe cada estado

O programa abaixo cria threads que ficam presas em estados diferentes, depois imprime em qual estado estão.

java— editable, runs on the server

O que extrair da execução:

  • Cada um dos seis estados era alcançável por código no mesmo programa. NEW e TERMINATED são os casos limites; os quatro do meio (RUNNABLE, BLOCKED, WAITING, TIMED_WAITING) são os que você verá em um dump de thread real.
  • A blocked-thread reportou BLOCKED porque o holder possuía o monitor synchronized. Se tivéssemos usado um ReentrantLock no lugar, o mesmo caminho de código teria reportado WAITING (pois Lock.lock() estaciona via LockSupport). O nome do estado diz que tipo de espera, não apenas "esta thread está presa."
  • A waiting-thread teria ficado em WAITING para sempre se main não tivesse chamado cond.notify(). O estado WAITING não tem timeout — alguém mais precisa acordá-la. É exatamente assim que um notify perdido produz um deadlock que nenhuma exceção jamais relata.
  • A thread que queimava CPU reportou RUNNABLE estivesse ela de fato em execução em um núcleo ou meramente sentada na fila de execução aguardando por um. A JVM não distingue "em execução" de "pronta"; o SO distingue. Se você precisar saber quais threads estão realmente consumindo CPU, use um profiler de amostragem — getState() não lhe dirá.
  • Após tRunning.join() retornar, seu estado era TERMINATED. Você ainda pode consultar seu nome, ID e objeto de estado, mas a thread acabou — isAlive() é false e start() lançaria uma exceção. Threads são de uso único: quando uma termina, você cria uma nova. (Esta é a principal motivação para o framework executor — um ExecutorService reutiliza a mesma thread do SO para muitas tarefas.)

O que vem a seguir

O próximo capítulo, Métodos de Thread Java, percorre a superfície de métodos de Threadsleep, join, yield, interrupt, holdsLock, e os utilitários estáticos — com as armadilhas de cada um.

Prática

Prática
Uma thread está no estado `BLOCKED`. O que ela está aguardando?
Uma thread está no estado `BLOCKED`. O que ela está aguardando?
Prática
Um dump de thread mostra uma thread como `RUNNABLE` com `socketRead0` no topo de sua stack. O que ela está realmente fazendo?
Um dump de thread mostra uma thread como `RUNNABLE` com `socketRead0` no topo de sua stack. O que ela está realmente fazendo?
Prática
Qual chamada deixa uma thread em `TIMED_WAITING` em vez de `WAITING`?
Qual chamada deixa uma thread em `TIMED_WAITING` em vez de `WAITING`?
Was this page helpful?