W3docs

Priorità dei Thread in Java

Le priorità dei thread Java sono un suggerimento, non una garanzia — cosa fa setPriority, cosa non fa e quando usarlo.

Thread porta una priorità — un intero da 1 a 10. La JVM la passa allo scheduler del sistema operativo come suggerimento su quale thread eseguire quando la CPU deve scegliere. Il suggerimento è solo quello — un suggerimento. Il sistema operativo è libero di ignorarlo e sulla maggior parte dei sistemi operativi desktop e server l'effetto è compreso tra impercettibile e invisibile. Questo capitolo spiega come appare l'API, cosa accade effettivamente sotto il cofano e i casi molto rari in cui impostare una priorità vale la pena.

L'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;

Tre costanti per la leggibilità — la maggior parte del codice che tocca la priorità usa queste anziché interi grezzi:

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();

Il costruttore usa la priorità del thread padre e la priorità massima del gruppo di thread del padre — di solito 5. Impostare un valore fuori dall'intervallo 1..10 lancia un'eccezione IllegalArgumentException.

Cosa significa "priorità" per la JVM

Due cose, nessuna delle quali è "questo thread verrà eseguito per primo":

  1. Un suggerimento di scheduling nativo. La JVM traduce il numero da 1 a 10 in qualcosa che il sistema operativo host comprende. Su Linux è un valore nice tramite pthread_setschedparam; su Windows è una costante THREAD_PRIORITY_*. La mappatura è definita dall'implementazione della JVM.
  2. Un limite massimo per gruppo di thread. Un thread non può essere impostato a una priorità superiore al maxPriority del suo gruppo di thread. I gruppi di thread sono per lo più deprecati, ma il limite si applica ancora.

Cosa la priorità non significa:

  • Non è un "blocco" — un thread ad alta priorità non può pre-emere un thread dentro una sezione critica.
  • Non è una garanzia di ordinamento. Due thread alla priorità 10 si contendono comunque la CPU; uno di essi la ottiene per primo.
  • Non influisce sulla correttezza. Qualsiasi programma che "funziona solo" grazie alle priorità ha un vero bug di sincronizzazione nascosto dietro.

Cosa fa effettivamente il sistema operativo

I diversi sistemi operativi host trattano il suggerimento in modo diverso. Il comportamento attuale, in linea di massima:

  • Linux. La configurazione predefinita della JVM tratta le priorità Java come valori nice. nice è un suggerimento allo scheduler CFS riguardo alla quota di CPU. Gli utenti non-root possono solo abbassare la priorità (nice positivo); alzarla (nice negativo) richiede CAP_SYS_NICE, che la maggior parte delle JVM non ha. Quindi in pratica, setPriority(MAX_PRIORITY) da un processo Java non-root è spesso un no-op.
  • Windows. Le priorità vengono mappate alle sette priorità di thread di Windows. La mappatura è più aggressiva; i thread ad alta priorità ottengono davvero più CPU. Ma non si può comunque pre-emere un thread dentro una syscall o che tiene un lock del kernel.
  • macOS. Usa pthread_setschedparam come Linux; comportamento simile.

La conclusione è la stessa su tutti: le priorità sono consigli, e su Linux il consiglio viene spesso ignorato del tutto. Non progettare per questo.

Quando usare effettivamente setPriority

Tre usi legittimi, in ordine di frequenza con cui si presentano:

  1. Contrassegnare un thread in background con MIN_PRIORITY così il sistema operativo lo deprioritizza dolcemente sotto carico. Un uploader di telemetria, un flusher di log, un job di rebuild della cache — nessuno di questi dovrebbe mai causare latenza visibile all'utente. Impostarli a 1 è un suggerimento gratuito che dice al sistema operativo "questo va bene privarlo un po' di risorse."
  2. Contrassegnare un helper hard-realtime con MAX_PRIORITY in una JVM a cui è stato dato il privilegio di usarlo. Raro al di fuori di audio, game loop o sistemi specializzati a bassa latenza.
  3. Non farlo. Il valore predefinito di NORM_PRIORITY è la risposta giusta per quasi ogni thread che creerai.

Cosa dovresti usare al suo posto

Se il motivo per cui stai pensando alle priorità è uno di questi — c'è uno strumento migliore:

VuoiUsa questo invece
"I job lunghi non devono bloccare quelli brevi"Due ExecutorService — uno piccolo per il lavoro sensibile alla latenza, uno grande per i batch
"Il thread A deve girare prima del thread B"join, un CountDownLatch, o una catena di CompletableFuture
"Solo un thread alla volta in questo metodo"synchronized o un ReentrantLock
"Evitare di affamare completamente il lavoro a bassa priorità"Una coda fair (new LinkedBlockingQueue<>() più una lotteria, o una PriorityBlockingQueue con priorità esplicita del task — non del thread)

Il pattern: la priorità è un suggerimento di scheduling tra thread eseguibili. Lo strumento giusto per "quale thread gira per primo" è quasi sempre la sincronizzazione, non la priorità.

Inversione di priorità

Il classico fallimento dello scheduling basato su priorità:

  • Un thread a bassa priorità L acquisisce il lock M.
  • Un thread ad alta priorità H tenta di acquisire M e rimane bloccato ad aspettare L.
  • Un thread a priorità media Med esegue lavoro CPU-bound, impedendo a L di essere schedulato.
  • H è di fatto alla priorità di Med — invertita.

Java non risolve questo per te. Il sistema nice del kernel nemmeno. La soluzione è (a) non fare affidamento sulle priorità per la correttezza, oppure (b) usare ReentrantLock con una politica di ordinamento fair e sezioni critiche brevi così la finestra di inversione è delimitata. Vedremo la fairness nel capitolo ReentrantLock.

Gruppi di thread (principalmente storici)

ThreadGroup gp = new ThreadGroup("workers");
gp.setMaxPriority(7);
Thread t = new Thread(gp, runnable, "worker");
t.setPriority(10);                                   // capped to 7 by the group

I gruppi di thread risalgono a Java 1.0 ed erano il "pannello di controllo" originale per lotti di thread. Sono stati quasi interamente sostituiti da ExecutorService e la maggior parte dei loro metodi è deprecata. Vedrai ThreadGroup in stack trace e dump; di solito non ne costruirai uno. L'unica parte ancora attiva è il limite maxPriority per gruppo.

Un esempio pratico: cercare di vedere le priorità in azione

Il programma seguente avvia diversi thread CPU-bound a diverse priorità e misura quanto lavoro ciascuno ha svolto in una finestra fissa. Su Linux probabilmente vedrai tutti fare quantità simili di lavoro — questo è il punto. Su Windows potresti vedere una differenza significativa. In ogni caso, la lezione è la stessa.

java— editable, runs on the server

Cosa ricavare dall'esecuzione:

  • Tutti e tre i thread quasi certamente hanno svolto quantità simili di lavoro, anche se uno era alla priorità 1 e un altro alla priorità 10. Su una JVM Linux in esecuzione come utente normale, la JVM non può effettivamente alzare la priorità sopra il valore predefinito — la chiamata setPriority(10) ha impostato il campo ma il sistema operativo lo ha trattato come la priorità normale. Il campo a livello Java cambia; lo scheduling effettivo quasi per niente.
  • La chiamata getPriority() ha riflesso il valore impostato, indipendentemente dal fatto che il sistema operativo lo abbia rispettato. Questo è importante durante il debug: il campo ti dice cosa il tuo codice ha chiesto, non cosa il sistema operativo ha fatto.
  • Il ciclo CPU intenzionalmente non si è mai bloccato (nessun Thread.sleep, nessun wait, nessun I/O). Questo è l'unico scenario in cui la priorità può plausibilmente avere importanza — pura competizione per la CPU. Non appena un thread si blocca, la priorità diventa irrilevante; il thread bloccato non è su una CPU comunque.
  • setPriority(11) ha lanciato IllegalArgumentException. I limiti sono applicati lato Java. Anche la priorità 0 lancia un'eccezione; l'intervallo valido è esattamente da 1 a 10.
  • La giusta conclusione dalla differenza piccola (o inesistente) nel conteggio dei batch è: quando il tuo design comincia a sembrare che abbia bisogno delle priorità per la correttezza, sostituiscilo con la sincronizzazione esplicita. Usa due ExecutorService se vuoi isolamento; usa i lock se vuoi ordinamento; usa le code se vuoi fairness. La priorità è un ultimo ricorso e su Linux è a malapena un ricorso.

Cosa fare dopo

Il prossimo capitolo, Java Synchronization, inizia la vera storia del codice concorrente sicuro — la parola chiave synchronized, i monitor intrinseci e come funziona il primitivo di lock di prima classe di Java.

Pratica

Pratica
Sei su Linux ed esegui un programma Java come utente normale. Chiami `t.setPriority(Thread.MAX_PRIORITY)` su un worker. Cosa succede effettivamente allo scheduling a livello OS di quel thread?
Sei su Linux ed esegui un programma Java come utente normale. Chiami `t.setPriority(Thread.MAX_PRIORITY)` su un worker. Cosa succede effettivamente allo scheduling a livello OS di quel thread?
Was this page helpful?