W3docs

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

  1. 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 nice via pthread_setschedparam; no Windows é uma constante THREAD_PRIORITY_*. O mapeamento é definido pela implementação da JVM.
  2. Um limite máximo por grupo de threads. Uma thread não pode ter prioridade maior que o maxPriority do 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 (nice positivo); aumentá-la (nice negativo) requer CAP_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_setschedparam como 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:

  1. Marque uma thread em segundo plano com MIN_PRIORITY para 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."
  2. Marque um auxiliar de tempo-real rígido com MAX_PRIORITY em uma JVM que recebeu o privilégio de usá-lo. Raro fora de áudio, loops de jogo, ou sistemas especializados de baixa latência.
  3. 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ê querUse 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 L adquire o lock M.
  • Uma thread de alta prioridade H tenta adquirir M e fica bloqueada esperando por L.
  • Uma thread de prioridade média Med executa trabalho vinculado à CPU, impedindo que L seja escalonada.
  • H fica efetivamente na prioridade de Med — 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 group

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

java— editable, runs on the server

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, sem wait, 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çou IllegalArgumentException. 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.

Prática

Prática
Você está no Linux e executa um programa Java como usuário regular. Você chama `t.setPriority(Thread.MAX_PRIORITY)` em um worker. O que realmente acontece com o escalonamento de nível de SO dessa thread?
Você está no Linux e executa um programa Java como usuário regular. Você chama `t.setPriority(Thread.MAX_PRIORITY)` em um worker. O que realmente acontece com o escalonamento de nível de SO dessa thread?
Was this page helpful?