Estados de Execução de uma Thread em Java
Quando você cria uma thread implementando Runnable ou estendendo Thread, ela passa por diferentes estados ao longo do ciclo de vida. O estado "executavel" ou de execução é apenas uma fase, e entender o que acontece antes e depois disso é o que separa código que funciona de código que entra em deadlock nas 3 da manhã.
na linguagem java uma thread executavel pode entrar em quais estados
A resposta curta é que uma thread thread-safe pode entrar nos estados de RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, NEW e TERMINATED. Mas a realidade prática é mais chata do que a teoria. Eu tive um problema específico há uns dois anos com uma aplicação de processamento paralelo. Threads pareciam travar sem motivo. Não era deadlock convencional - elas estavam simplesmente em estado WAITING. O culpado? Um wait() sem notify() em uma fila de trabalho compartilhada, porque eu esqueci de sincronizar o acesso ao recurso. A thread entrou no estado WAITING e nunca saiu. Demorei três dias para perceber porque o stack trace mostrava que a thread estava viva, apenas parada esperando algo que nunca viria.
O estado RUNNABLE é o que as pessoas costumam chamar de "executando". Na verdade, na JVM Java, RUNNABLE significa que a thread está pronta para rodar ou realmente rodando. O escalonador do sistema operacional decide quando ela efetivamente executa bytecode. Isso importa porque você não tem controle direto sobre isso. Quando uma thread chama wait() no objeto Monitor, ela vai para WAITING. Entra no monito espera. Se alguém chamar notify() ou notifyAll() no mesmo objeto, a thread competirá para adquirir o lock e voltar para RUNNABLE. O problema é que se ninguém chamar notify, essa thread fica lá para sempre.
BLOCKED acontece quando uma thread tenta entrar em um bloco sincronizado ou método sincronizado mas o monitor já está travado por outra thread. É diferente de WAITING. Em BLOCKED a thread está competindo ativamente por um recurso. Em WAITING ela está esperando explicitamente por um sinal. NEW é o estado inicial quando você instancia Thread ou Runnable mas ainda não chamou start(). Muitas pessoas chamam run() diretamente e se perguntam por que a thread não é executada concorrentemente. Chamar run() é apenas invocar um método normal. Você precisa de start() para criar a execução paralela.
TERMINATED é quando a thread terminou. Seja por completion normal, exceção não tratada, ou interrupt. Thread interrompida com join() pode gerar confusão aqui porque a thread pode estar terminado mas você ainda está esperando no join. O estado TIMED_WAITING ocorre com sleep(), wait(timeout), join(timeout), ou LockSupport.parkNanos(). Diferente de WAITING, esse estado tem timeout. A thread volta para RUNNABLE automaticamente quando o tempo acaba, desde que o lock esteja disponível se for um bloco sincronizado.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Uma nuance que beginners ignoram: um objeto que chama interrupt() em uma thread em WAITING ou TIMED_WAITING lança InterruptedException na thread visada. A thread não pára magicamente. Você precisa capturar essa exception e decidir o que fazer. Se você capturar e apenas logar sem tratar, a thread continua executando normalmente. O flag de interrupted é resetado. O problema com interrupt() é que thread interrompida não significa thread morta. Significa apenas que o flag foi setado. Se a thread estiver em operações de I/O bloqueantes como read() ou accept(), o comportamento depende da implementação. Alguns canais lançam ClosedByInterruptException, outros não.
Na prática, eu recomendo usar ThreadPoolExecutor com Future em vez de gerenciar threads manualmente. Você ganha controle sobre lifecycle, pode cancelar tarefas, e evita problemas com threads órfãs que nunca são coletadas. Além disso, daemon threads são um risco silencioso - se todas as non-daemon threads terminarem, as daemon threads são mortas imediatamente, sem graceful shutdown. Se você precisa de threads que precisam terminar limpo, implemente o padrão de interrupção: verifique Thread.currentThread().isInterrupted() periodicamente e saia do loop de trabalho. Se estiver usando wait()/notify(), use notifyAll() em vez de notify() para evitar lost signals. E sempre use try-finally para garantir que locks sejam liberados.
Existe uma armadilha comum com synchronized: se uma thread chama wait() dentro de um bloco sincronizado, ela libera o lock temporariamente. Isso é intencional e necessário. Mas se você esquecer de reacquire o lock após o wait retornar e tentar acessar estado compartilhado, vai ter race conditions. O estado WAITING não protege seu dado. Threads daemon são outro ponto. Elas não impedem a JVM de sair. Isso é útil para threads de background como garbage collection interna, mas perigoso para workers de aplicação. Se você criar uma thread daemon para processar filas e o main thread termina, sua thread morre junto sem aviso.
O estado de uma thread pode ser inspecionado com getState(), mas isso é apenas um snapshot. Thread states mudam rapidamente em sistemas concorrentes. Usar getState() para lógica de controle é anti-pattern. Use barriers, CountDownLatch, ou CompletableFuture para coordenação. Para monitorar threads em produção, JMX é a forma padrão. ThreadMXBean permite obter heap dumps de threads, detectar deadlocks, e contar threads ativas. Isso é muito mais confiável do que tentar ler o estado diretamente, porque o GC e o JIT podem afetar timing de observação.
Resumindo o que acontece na prática: thread New -> start() -> Runnable (pode alternar entre Running e Blocked) -> Wait/Timed_Wait se chamar métodos de espera -> Terminated quando acaba. O ciclo não é linear e loops são comuns. Cada transição de estado precisa ser tratada corretamente para evitar vazamentos e deadlocks.