Prioridade de Threads em Java
Prioridades de threads em Java são uma dica, não uma garantia — o que setPriority faz, o que não faz, e quando usá-lo.
Thread carrega uma prioridade — um inteiro de 1 a 10. A JVM a repassa ao escalonador do SO como uma dica sobre qual thread executar quando a CPU precisa escolher. A dica é apenas isso — uma dica. O SO é livre para ignorá-la, e na maioria dos sistemas operacionais desktop e servidor o efeito fica entre sutil e invisível. Este capítulo explica como é a API, o que realmente acontece por baixo, e os casos muito específicos em que definir uma prioridade vale o trabalho.
A API
public final void setPriority(int newPriority);
public final int getPriority();
public static final int MIN_PRIORITY = 1;
public static final int NORM_PRIORITY = 5;
public static final int MAX_PRIORITY = 10;Três constantes para legibilidade — a maior parte do código que trabalha com prioridade usa estas em vez de inteiros brutos:
Thread t = new Thread(this::housekeeping, "gc-poker");
t.setPriority(Thread.MIN_PRIORITY); // 1 — "background"
t.start();
Thread h = new Thread(this::handleRequest, "http");
h.setPriority(Thread.NORM_PRIORITY); // 5 — default
h.start();O construtor usa a prioridade da thread pai e a prioridade máxima do grupo de threads da thread pai — geralmente 5. Definir um valor fora de 1..10 lança IllegalArgumentException.
O que "prioridade" significa para a JVM
Duas coisas, nenhuma das quais é "esta thread executará primeiro":
- Uma dica de escalonamento nativo. A JVM traduz o número de 1 a 10 em algo que o SO hospedeiro entende. No Linux isso é um valor
niceviapthread_setschedparam; no Windows é uma constanteTHREAD_PRIORITY_*. O mapeamento é definido pela implementação da JVM. - Um limite máximo por grupo de threads. Uma thread não pode ter prioridade maior que o
maxPrioritydo seu grupo de threads. Grupos de threads estão em grande parte obsoletos, mas o limite ainda se aplica.
O que prioridade não significa:
- Não é um "lock" — uma thread de alta prioridade não pode preemptar uma thread dentro de uma seção crítica.
- Não é uma garantia de ordenação. Duas threads com prioridade 10 ainda competem; uma delas obtém a CPU primeiro.
- Não afeta a correção. Qualquer programa que "só funciona" por causa das prioridades tem um bug real de sincronização se escondendo por trás.
O que o SO realmente faz
Diferentes SOs hospedeiros tratam a dica de forma diferente. O comportamento atual, aproximadamente:
- Linux. A configuração padrão da JVM trata as prioridades Java como valores
nice.niceé uma dica ao escalonador CFS sobre o compartilhamento de CPU. Usuários não-root só podem diminuir a prioridade (nicepositivo); aumentá-la (nicenegativo) requerCAP_SYS_NICE, que a maioria das JVMs não possui. Portanto, na prática,setPriority(MAX_PRIORITY)de um processo Java não-root muitas vezes não tem efeito. - Windows. As prioridades mapeiam para as sete prioridades de thread do Windows. O mapeamento é mais agressivo; threads de alta prioridade realmente obtêm mais CPU. Mas ainda não é possível preemptar uma thread dentro de uma syscall ou segurando um lock do kernel.
- macOS. Usa
pthread_setschedparamcomo o Linux; comportamento semelhante.
A conclusão é a mesma em todos eles: prioridades são conselhos, e no Linux o conselho é frequentemente ignorado por completo. Não projete dependendo disso.
Quando realmente usar setPriority
Três usos legítimos, em ordem de frequência com que surgem:
- Marque uma thread em segundo plano com
MIN_PRIORITYpara que o SO a desprioritize suavemente sob carga. Um enviador de telemetria, um flusher de log, uma tarefa de reconstrução de cache — nenhum desses deve jamais causar latência para o usuário. Definir para 1 é uma dica gratuita que diz ao SO "este pode ser privado de recursos por um tempo." - Marque um auxiliar de tempo-real rígido com
MAX_PRIORITYem uma JVM que recebeu o privilégio de usá-lo. Raro fora de áudio, loops de jogo, ou sistemas especializados de baixa latência. - Não use. O padrão de
NORM_PRIORITYé a resposta certa para quase toda thread que você jamais criará.
O que usar em vez disso
Se o motivo pelo qual você está pensando em prioridades é um destes — há uma ferramenta melhor:
| Você quer | Use isto em vez disso |
|---|---|
| "Jobs longos não devem bloquear jobs curtos" | Dois ExecutorServices — um pequeno para trabalhos sensíveis à latência, um grande para lote |
| "Thread A deve executar antes de Thread B" | join, um CountDownLatch, ou uma cadeia CompletableFuture |
| "Apenas uma thread por vez neste método" | synchronized ou um ReentrantLock |
| "Evitar que trabalho de baixa prioridade seja completamente privado de recursos" | Uma fila justa (new LinkedBlockingQueue<>() com sorteio, ou um PriorityBlockingQueue com prioridade explícita de tarefa — não de thread) |
O padrão: prioridade é uma dica de escalonamento entre threads executáveis. A ferramenta certa para "qual thread executa primeiro" é quase sempre sincronização, não prioridade.
Inversão de prioridade
O modo clássico de falha do escalonamento baseado em prioridade:
- Uma thread de baixa prioridade
Ladquire o lockM. - Uma thread de alta prioridade
Htenta adquirirMe fica bloqueada esperando porL. - Uma thread de prioridade média
Medexecuta trabalho vinculado à CPU, impedindo queLseja escalonada. Hfica efetivamente na prioridade deMed— invertida.
Java não resolve isso para você. O sistema nice do kernel também não. A correção é: (a) não depender de prioridades para correção, ou (b) usar ReentrantLock com política de ordenação justa e seções críticas curtas para que a janela de inversão seja limitada. Veremos a justiça no capítulo ReentrantLock.
Grupos de threads (principalmente histórico)
ThreadGroup gp = new ThreadGroup("workers");
gp.setMaxPriority(7);
Thread t = new Thread(gp, runnable, "worker");
t.setPriority(10); // capped to 7 by the groupGrupos de threads datam do Java 1.0 e eram o "painel de controle" original para lotes de threads. Foram quase inteiramente substituídos por ExecutorService e a maioria de seus métodos está obsoleta. Você verá ThreadGroup em stack traces e dumps; normalmente não construirá um. A única parte ainda ativa é o limite de maxPriority por grupo.
Um exemplo trabalhado: tente ver as prioridades em ação
O programa abaixo cria várias threads vinculadas à CPU com prioridades diferentes e mede quanto trabalho cada uma fez em uma janela fixa. No Linux você provavelmente verá todas fazendo quantidades semelhantes de trabalho — esse é o ponto. No Windows você pode ver uma diferença significativa. De qualquer forma, a lição é a mesma.
O que extrair da execução:
- As três threads quase certamente fizeram quantidades semelhantes de trabalho, mesmo que uma estivesse na prioridade 1 e outra na prioridade 10. Em uma JVM Linux executando como usuário normal, a JVM não pode realmente elevar a prioridade acima do padrão — a chamada
setPriority(10)definiu o campo, mas o SO a tratou como prioridade normal. O campo no nível Java muda; o escalonamento real mal muda. - A chamada
getPriority()refletiu o valor que você definiu, independentemente de o SO ter respeitado isso. Isso é importante ao depurar: o campo informa o que seu código pediu, não o que o SO fez. - O loop de CPU intencionalmente nunca bloqueou (sem
Thread.sleep, semwait, sem I/O). Esse é o único cenário em que a prioridade pode ter algum efeito — competição pura de CPU. Assim que uma thread bloqueia, a prioridade se torna irrelevante; a thread bloqueada não está em uma CPU de qualquer forma. setPriority(11)lançouIllegalArgumentException. Os limites são aplicados no lado Java. Prioridade 0 também lança; o intervalo válido é exatamente de 1 a 10.- A conclusão certa da pequena diferença (ou inexistente) nas contagens de lotes é: quando seu design começa a parecer que precisa de prioridades para correção, substitua-o por sincronização explícita. Use dois
ExecutorServices se quiser isolamento; use locks se quiser ordenação; use filas se quiser justiça. Prioridade é o último recurso, e no Linux mal é um recurso.
O que vem a seguir
O próximo capítulo, Java Synchronization, começa a história real do código concorrente seguro — a palavra-chave synchronized, monitores intrínsecos, e como o primitivo de lock de primeira classe do Java funciona.