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":
- 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
nicemediantepthread_setschedparam; en Windows es una constanteTHREAD_PRIORITY_*. El mapeo está definido por la implementación de la JVM. - Un límite máximo por grupo de hilos. Un hilo no puede establecerse a una prioridad mayor que la
maxPriorityde 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.nicees una sugerencia al planificador CFS sobre la cuota de CPU. Los usuarios no root solo pueden bajar la prioridad (nicepositivo); subirla (nicenegativo) requiereCAP_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_setschedparamcomo 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:
- Marcar un hilo en segundo plano con
MIN_PRIORITYpara 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". - Marcar un helper de tiempo real estricto con
MAX_PRIORITYen 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. - No hacerlo. El valor predeterminado de
NORM_PRIORITYes 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 quieres | Usa 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
Ladquiere el lockM. - Un hilo de alta prioridad
Hintenta adquirirMy se bloquea esperando aL. - Un hilo de prioridad media
Medejecuta trabajo intensivo en CPU, impidiendo queLsea planificado. Hestá efectivamente a la prioridad deMed— 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 groupLos 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.
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, sinwait, 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.