Priority Scheduling & Starvation
IntermediatePriority scheduling assigns each process a priority and always runs the highest-priority process next, but risks starvation of low-priority processes — solved by aging.
Overview
In priority scheduling, each process is assigned a numeric priority. The CPU is allocated to the process with the highest priority (lowest number in most OS conventions). Priority scheduling can be preemptive (a new high-priority arrival preempts the running process) or non-preemptive (current process runs to completion). Static priorities are assigned once and never change. Dynamic priorities change over time. The critical problem is starvation (indefinite postponement): a continuous stream of high-priority processes can prevent low-priority ones from ever running. The classic solution is aging: gradually increasing the priority of processes that have been waiting a long time. Priority inversion is a famous bug: a high-priority task is blocked waiting for a resource held by a low-priority task. The Mars Pathfinder mission experienced this in 1997, solved by priority inheritance.
Starvation and Aging
Starvation is the indefinite postponement of low-priority processes. It is like a hospital triage system where critical patients always go first — a stable patient admitted on Monday may still be waiting on Friday if critical cases keep arriving. Aging solves this by incrementally boosting the priority of waiting processes. After waiting T time units, a process's effective priority increases, eventually becoming high enough to run.
// Priority scheduling with aging to prevent starvation
// Lower number = higher priority (convention)
class Process {
int id, basePriority, effectivePriority, waitingTime;
Process(int id, int priority) {
this.id = id;
this.basePriority = priority;
this.effectivePriority = priority;
this.waitingTime = 0;
}
}
List<Process> readyQueue = new ArrayList<>();
readyQueue.add(new Process(1, 1)); // high priority
readyQueue.add(new Process(2, 10)); // low priority — risk of starvation
readyQueue.add(new Process(3, 5)); // medium priority
int AGING_INTERVAL = 3; // boost priority every 3 time units
int agingBoost = 1; // increase effective priority by 1 per interval
// Simulate aging: every tick, boost waiting processes' effective priority
for (int tick = 0; tick < 15; tick++) {
for (Process p : readyQueue) {
p.waitingTime++;
if (p.waitingTime % AGING_INTERVAL == 0) {
p.effectivePriority = Math.max(1,
p.effectivePriority - agingBoost); // lower number = higher priority
System.out.printf("Tick %2d: P%d priority boosted to %d%n",
tick, p.id, p.effectivePriority);
}
}
}
// Eventually, low-priority P2 (base=10) ages up to compete with high-priority tasksJava Thread Priority and Priority Inversion
Java provides Thread.setPriority(int) with values 1 (MIN_PRIORITY) to 10 (MAX_PRIORITY), default 5 (NORM_PRIORITY). However, the mapping to OS priorities is JVM and OS dependent — on Linux, Java thread priorities may be ignored entirely. The correct way to achieve priority-based behavior in Java is via explicit scheduling with PriorityBlockingQueue or ReentrantLock(fair=true). Priority inversion occurs when a high-priority thread is blocked on a resource held by a low-priority thread, while a medium-priority thread runs instead — the Mars Pathfinder bug.
// Java Thread priorities — advisory only, OS-dependent
Thread high = new Thread(() -> System.out.println("High priority task"));
high.setPriority(Thread.MAX_PRIORITY); // 10
Thread low = new Thread(() -> System.out.println("Low priority task"));
low.setPriority(Thread.MIN_PRIORITY); // 1
// WARNING: On Linux with default JVM settings, these priorities are often ignored
// Use explicit prioritization with PriorityBlockingQueue for reliable behavior
// Priority-aware task queue — reliably prioritized
PriorityBlockingQueue<Runnable> priorityQueue = new PriorityBlockingQueue<>(
10, Comparator.comparingInt(r -> ((PrioritizedTask) r).priority)
);
record PrioritizedTask(int priority, Runnable work) implements Runnable, Comparable<PrioritizedTask> {
public void run() { work.run(); }
public int compareTo(PrioritizedTask other) {
return Integer.compare(this.priority, other.priority); // lower = higher priority
}
}
priorityQueue.add(new PrioritizedTask(5, () -> System.out.println("Medium task")));
priorityQueue.add(new PrioritizedTask(1, () -> System.out.println("Critical task")));
priorityQueue.add(new PrioritizedTask(9, () -> System.out.println("Low task")));
// Drain in priority order
while (!priorityQueue.isEmpty()) priorityQueue.poll().run();
// Output: Critical → Medium → LowKey Points to Remember
- 1Priority scheduling always runs the highest-priority ready process; can be preemptive or non-preemptive.
- 2Starvation: low-priority processes may never run if high-priority tasks continuously arrive.
- 3Aging: gradually increase the effective priority of waiting processes to prevent starvation.
- 4Priority inversion: high-priority task blocked by low-priority task holding a shared resource.
- 5Priority inheritance protocol: low-priority task inherits the high priority of whoever is waiting on it.
- 6Java Thread.setPriority() is advisory — OS mapping is JVM and platform dependent.
Interview Questions
Sign in to ask AriaWhat is priority inversion and how does priority inheritance solve it?
What is starvation in scheduling and how does aging prevent it?
Why is Thread.setPriority() unreliable on Linux JVMs?
Describe the Mars Pathfinder priority inversion bug.
Ask Aria about Priority Scheduling & Starvation
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.