Java Thread Priority
Java thread priorities are a hint, not a guarantee — what setPriority actually does, what it doesn't, and when to use it.
Java Thread Priority
Thread carries a priority — an integer from 1 to 10. The JVM passes it to the OS scheduler as a hint about which thread to run when the CPU has to pick. The hint is just that — a hint. The OS is free to ignore it, and on most desktop and server OSes the effect is somewhere between subtle and invisible. This chapter explains what the API looks like, what actually happens underneath, and the very narrow cases where setting a priority is worth the bother.
The 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;Three constants for readability — most code that touches priority uses these rather than raw integers:
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();The constructor uses the parent thread's priority and the parent's thread group's max priority — usually 5. Setting it to a value outside 1..10 throws IllegalArgumentException.
What "priority" means to the JVM
Two things, neither of which is "this thread will run first":
- A native scheduling hint. The JVM translates the 1–10 number into something the host OS understands. On Linux that's a
nicevalue viapthread_setschedparam; on Windows it's aTHREAD_PRIORITY_*constant. The mapping is JVM-implementation-defined. - A maximum per-thread-group cap. A thread can't be set to a priority higher than its thread group's
maxPriority. Thread groups are mostly deprecated, but the cap still applies.
What priority does not mean:
- It's not a "lock" — a high-priority thread cannot pre-empt a thread inside a critical section.
- It's not a guarantee of ordering. Two threads at priority 10 still race; one of them gets the CPU first.
- It does not affect correctness. Any program that "only works" because of priorities has a real synchronisation bug hiding behind it.
What the OS actually does
Different host OSes treat the hint differently. The current behaviour, roughly:
- Linux. Default JVM configuration treats Java priorities as
nicevalues.niceis a hint to the CFS scheduler about CPU share. Non-root users can only lower priority (positivenice); raising it (negativenice) requiresCAP_SYS_NICE, which most JVMs don't have. So in practice,setPriority(MAX_PRIORITY)from a non-root Java process is often a no-op. - Windows. Priorities map to the seven Windows thread priorities. The mapping is more aggressive; high-priority threads do actually get more CPU. But you still can't preempt a thread inside a syscall or holding a kernel lock.
- macOS. Uses
pthread_setschedparamlike Linux; behaviour similar.
The takeaway is the same on all of them: priorities are advice, and on Linux the advice is often ignored entirely. Don't design for it.
When to actually use setPriority
Three legitimate uses, in order of how often they come up:
- Mark a background thread
MIN_PRIORITYso the OS gently deprioritises it under load. A telemetry uploader, a log flusher, a cache-rebuild job — none of these should ever cause user-facing latency. Setting them to 1 is a free hint that tells the OS "this one is okay to starve a bit." - Mark a hard-realtime helper
MAX_PRIORITYin a JVM that's been given the privilege to use it. Rare outside of audio, game loops, or specialised low-latency systems. - Don't. The default of
NORM_PRIORITYis the right answer for almost every thread you'll ever create.
What you should use instead
If the reason you're thinking about priorities is one of these — there's a better tool:
| You want | Use this instead |
|---|---|
| "Long jobs shouldn't block short jobs" | Two ExecutorServices — a small one for latency-sensitive work, a large one for batch |
| "Thread A must run before thread B" | join, a CountDownLatch, or a CompletableFuture chain |
| "Only one thread at a time in this method" | synchronized or a ReentrantLock |
| "Avoid starving low-priority work entirely" | A fair queue (new LinkedBlockingQueue<>() plus a lottery, or a PriorityBlockingQueue with explicit task priority — not thread priority) |
The pattern: priority is a scheduling hint between runnable threads. The right tool for "which thread runs first" is almost always synchronisation, not priority.
Priority inversion
The classic failure mode of priority-based scheduling:
- A low-priority thread
Lacquires lockM. - A high-priority thread
Htries to acquireMand blocks waiting forL. - A medium-priority thread
Medruns CPU-bound work, preventingLfrom getting scheduled. His effectively atMed's priority — inverted.
Java doesn't solve this for you. The kernel's nice system doesn't either. The fix is either (a) don't rely on priorities for correctness, or (b) use ReentrantLock with a fair-ordering policy and short critical sections so the inversion window is bounded. We'll see fairness in the ReentrantLock chapter.
Thread groups (mostly historical)
ThreadGroup gp = new ThreadGroup("workers");
gp.setMaxPriority(7);
Thread t = new Thread(gp, runnable, "worker");
t.setPriority(10); // capped to 7 by the groupThread groups date from Java 1.0 and were the original "control panel" for batches of threads. They've been almost entirely replaced by ExecutorService and most of their methods are deprecated. You'll see ThreadGroup in stack traces and dumps; you won't typically construct one. The one piece still active is the per-group maxPriority cap.
A worked example: try to see priorities at work
The program below spawns several CPU-bound threads at different priorities and measures how much work each got done in a fixed window. On Linux you'll likely see them all do similar amounts of work — that's the point. On Windows you may see a meaningful difference. Either way, the lesson is the same.
What to take from the run:
- All three threads almost certainly did similar amounts of work, even though one was at priority 1 and another at priority 10. On a Linux JVM running as a normal user, the JVM can't actually raise priority above default — the
setPriority(10)call set the field but the OS treated it as the normal priority. The Java-level field changes; the actual scheduling barely does. - The
getPriority()call reflected the value you set, regardless of whether the OS honored it. That's important when debugging: the field tells you what your code asked for, not what the OS did. - The CPU loop intentionally never blocked (no
Thread.sleep, nowait, no I/O). That's the only scenario where priority can plausibly matter — pure CPU competition. As soon as a thread blocks, the priority becomes irrelevant; the blocked thread isn't on a CPU anyway. setPriority(11)threwIllegalArgumentException. The bounds are enforced on the Java side. Priority 0 also throws; the valid range is exactly 1 through 10.- The right takeaway from the small (or non-existent) difference in batch counts is: when your design starts to feel like it needs priorities for correctness, replace it with explicit synchronisation. Use two
ExecutorServices if you want isolation; use locks if you want ordering; use queues if you want fairness. Priority is a last resort, and on Linux it's barely a resort at all.
What's next
The next chapter, Java Synchronization, starts the real story of safe concurrent code — the synchronized keyword, intrinsic monitors, and how Java's first-class lock primitive works.
Practice
You're on Linux and run a Java program as a regular user. You call `t.setPriority(Thread.MAX_PRIORITY)` on a worker. What actually happens to the OS-level scheduling of that thread?