W3docs

Prioridad de hilos en Java

Las prioridades de hilos en Java son una sugerencia, no una garantía: qué hace realmente setPriority, qué no hace y cuándo usarlo.

Thread lleva una prioridad — un entero del 1 al 10. La JVM lo pasa al planificador del SO como una sugerencia sobre qué hilo ejecutar cuando la CPU debe elegir. La sugerencia es exactamente eso — una sugerencia. El SO es libre de ignorarla, y en la mayoría de los SO de escritorio y servidor el efecto va de sutil a invisible. Este capítulo explica cómo se ve la API, qué ocurre realmente por debajo y los casos muy concretos en los que establecer una prioridad vale la pena.

La 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;

Tres constantes para mayor legibilidad — la mayoría del código que toca la prioridad usa estas en lugar de enteros directos:

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

El constructor usa la prioridad del hilo padre y la prioridad máxima del grupo de hilos del padre — normalmente 5. Establecerla a un valor fuera de 1..10 lanza IllegalArgumentException.

Qué significa "prioridad" para la JVM

Dos cosas, ninguna de las cuales es "este hilo se ejecutará primero":

  1. Una sugerencia de planificación nativa. La JVM traduce el número del 1 al 10 a algo que el SO anfitrión entiende. En Linux es un valor nice mediante pthread_setschedparam; en Windows es una constante THREAD_PRIORITY_*. El mapeo está definido por la implementación de la JVM.
  2. Un límite máximo por grupo de hilos. Un hilo no puede establecerse a una prioridad mayor que la maxPriority de su grupo de hilos. Los grupos de hilos están en su mayoría obsoletos, pero el límite sigue aplicándose.

Lo que la prioridad no significa:

  • No es un "candado" — un hilo de alta prioridad no puede desalojar a un hilo dentro de una sección crítica.
  • No es una garantía de orden. Dos hilos con prioridad 10 siguen compitiendo; uno de ellos obtiene la CPU primero.
  • No afecta a la corrección. Cualquier programa que "solo funciona" por las prioridades tiene un bug real de sincronización escondido detrás.

Lo que el SO hace realmente

Los distintos SO anfitriones tratan la sugerencia de forma diferente. El comportamiento actual, a grandes rasgos:

  • Linux. La configuración predeterminada de la JVM trata las prioridades de Java como valores nice. nice es una sugerencia al planificador CFS sobre la cuota de CPU. Los usuarios no root solo pueden bajar la prioridad (nice positivo); subirla (nice negativo) requiere CAP_SYS_NICE, que la mayoría de las JVM no tienen. Así que en la práctica, setPriority(MAX_PRIORITY) desde un proceso Java sin privilegios de root suele ser una no-operación.
  • Windows. Las prioridades se mapean a las siete prioridades de hilos de Windows. El mapeo es más agresivo; los hilos de alta prioridad realmente obtienen más CPU. Pero aún así no se puede desalojar un hilo dentro de una syscall o que mantiene un lock del kernel.
  • macOS. Usa pthread_setschedparam como Linux; comportamiento similar.

La conclusión es la misma en todos ellos: las prioridades son consejos, y en Linux el consejo se ignora con frecuencia por completo. No diseñes dependiendo de ello.

Cuándo usar realmente setPriority

Tres usos legítimos, en orden de frecuencia con que aparecen:

  1. Marcar un hilo en segundo plano con MIN_PRIORITY para que el SO lo despriorice suavemente bajo carga. Un cargador de telemetría, un escritor de logs, un trabajo de reconstrucción de caché — ninguno de estos debería causar jamás latencia visible al usuario. Establecerlos a 1 es una sugerencia gratuita que le dice al SO "este puede esperar un poco".
  2. Marcar un helper de tiempo real estricto con MAX_PRIORITY en una JVM a la que se le ha dado el privilegio para usarlo. Raro fuera de audio, bucles de juego o sistemas especializados de baja latencia.
  3. No hacerlo. El valor predeterminado de NORM_PRIORITY es la respuesta correcta para casi todos los hilos que crearás.

Qué deberías usar en su lugar

Si la razón por la que estás pensando en prioridades es una de estas — hay una herramienta mejor:

Lo que quieresUsa esto en su lugar
"Los trabajos largos no deben bloquear los cortos"Dos ExecutorServices — uno pequeño para trabajo sensible a la latencia, uno grande para procesamiento por lotes
"El hilo A debe ejecutarse antes que el hilo B"join, un CountDownLatch o una cadena de CompletableFuture
"Solo un hilo a la vez en este método"synchronized o un ReentrantLock
"Evitar inanición total del trabajo de baja prioridad"Una cola justa (new LinkedBlockingQueue<>() más una lotería, o un PriorityBlockingQueue con prioridad explícita de tarea — no de hilo)

El patrón: la prioridad es una sugerencia de planificación entre hilos ejecutables. La herramienta correcta para "qué hilo se ejecuta primero" es casi siempre la sincronización, no la prioridad.

Inversión de prioridad

El modo de fallo clásico de la planificación basada en prioridades:

  • Un hilo de baja prioridad L adquiere el lock M.
  • Un hilo de alta prioridad H intenta adquirir M y se bloquea esperando a L.
  • Un hilo de prioridad media Med ejecuta trabajo intensivo en CPU, impidiendo que L sea planificado.
  • H está efectivamente a la prioridad de Med — invertida.

Java no resuelve esto por ti. El sistema nice del kernel tampoco. La solución es (a) no depender de las prioridades para la corrección, o (b) usar ReentrantLock con una política de orden justo y secciones críticas cortas para que la ventana de inversión esté acotada. Veremos la equidad en el capítulo de ReentrantLock.

Grupos de hilos (mayormente 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

Los grupos de hilos datan de Java 1.0 y fueron el "panel de control" original para lotes de hilos. Han sido reemplazados casi por completo por ExecutorService y la mayoría de sus métodos están obsoletos. Verás ThreadGroup en trazas de pila y volcados; normalmente no construirás uno. La única parte que sigue activa es el límite de maxPriority por grupo.

Un ejemplo práctico: intentar ver las prioridades en acción

El programa siguiente lanza varios hilos intensivos en CPU a distintas prioridades y mide cuánto trabajo hizo cada uno en una ventana fija. En Linux probablemente verás que todos hacen cantidades similares de trabajo — ese es precisamente el punto. En Windows puede que veas una diferencia significativa. De cualquier forma, la lección es la misma.

java— editable, runs on the server

Qué extraer de la ejecución:

  • Los tres hilos casi con toda seguridad realizaron cantidades similares de trabajo, aunque uno estaba en prioridad 1 y otro en prioridad 10. En una JVM de Linux ejecutada como usuario normal, la JVM no puede elevar la prioridad por encima de la predeterminada — la llamada a setPriority(10) estableció el campo pero el SO lo trató como la prioridad normal. El campo a nivel Java cambia; la planificación real apenas lo hace.
  • La llamada a getPriority() reflejó el valor que estableciste, independientemente de si el SO lo respetó. Eso es importante al depurar: el campo te dice lo que tu código pidió, no lo que el SO hizo.
  • El bucle de CPU intencionalmente nunca se bloqueó (sin Thread.sleep, sin wait, sin E/S). Ese es el único escenario donde la prioridad puede tener importancia — competencia pura de CPU. En cuanto un hilo se bloquea, la prioridad se vuelve irrelevante; el hilo bloqueado no está en una CPU de todas formas.
  • setPriority(11) lanzó IllegalArgumentException. Los límites se verifican en el lado de Java. La prioridad 0 también lanza; el rango válido es exactamente del 1 al 10.
  • La conclusión correcta de la pequeña (o inexistente) diferencia en el recuento de lotes es: cuando tu diseño empiece a parecer que necesita prioridades para ser correcto, sustitúyelo por sincronización explícita. Usa dos ExecutorServices si quieres aislamiento; usa locks si quieres orden; usa colas si quieres equidad. La prioridad es el último recurso, y en Linux apenas es un recurso en absoluto.

Qué sigue

El siguiente capítulo, Java Synchronization, comienza la historia real del código concurrente seguro — la palabra clave synchronized, los monitores intrínsecos y cómo funciona la primitiva de lock nativa de Java.

Práctica

Práctica
Estás en Linux y ejecutas un programa Java como usuario normal. Llamas a `t.setPriority(Thread.MAX_PRIORITY)` en un worker. ¿Qué ocurre realmente con la planificación a nivel de SO de ese hilo?
Estás en Linux y ejecutas un programa Java como usuario normal. Llamas a `t.setPriority(Thread.MAX_PRIORITY)` en un worker. ¿Qué ocurre realmente con la planificación a nivel de SO de ese hilo?
Was this page helpful?