What causes thread starvation in Java, and how do fairness settings and thread priorities affect it?
answer
- Starvation = runnable thread perpetually denied a lock or CPU
- Default synchronized & ReentrantLock are UNFAIR (barging) → can starve waiters
- new ReentrantLock(true) = fair FIFO, no starvation, lower throughput
- Java thread priorities are advisory/platform-dependent — never rely on them
- Keep critical sections short; synchronized has no fairness knob
basics
~20 sStarvation happens when a thread never gets a resource it needs. In Java it can come from a thread holding a lock too long, unfair locks that let other threads barge ahead, or low thread priority making the scheduler skip it. Using a fair lock can prevent lock starvation.
solid answer
~50 sStarvation is a thread being perpetually denied the CPU or a lock. In Java the common causes are: a greedy thread that holds a shared lock for long stretches; the default unfair lock policy where ReentrantLock and synchronized let a thread 'barge' and reacquire a lock just released, repeatedly skipping waiters; and thread priorities, since a flood of higher-priority threads can starve lower-priority ones (though Java priorities are advisory and map unevenly across OSes). The main tool is fairness: new ReentrantLock(true) grants the lock to the longest-waiting thread (FIFO), guaranteeing no waiter is starved — at a throughput cost from reduced barging. ReentrantReadWriteLock and the Semaphore also have fair modes. synchronized has no fairness knob. Beyond fairness, keep critical sections short, avoid relying on priorities for correctness, and prefer concurrent collections or work queues that distribute fairly.
go deeper
Knows starvation means a thread never gets a needed resource and that it differs from deadlock.
Names concrete causes (greedy lock holder, low priority) and knows a fair lock exists to prevent lock starvation.
Explains barging vs FIFO fairness and its throughput trade-off, knows which Java constructs offer fair modes (ReentrantLock/ReadWriteLock/Semaphore) and that synchronized doesn't, and treats priorities as advisory.
Decides fairness policy per subsystem balancing tail latency vs throughput, designs work distribution (queues/executors) to avoid starvation system-wide, and reasons about cross-platform priority behavior and SLA guarantees.
## What starvation is **Starvation** is the liveness failure where a thread is **runnable** (ready to do work) but is **perpetually denied a resource it needs** — either CPU time or a lock. Unlike deadlock there is no cycle of mutual waiting, and unlike livelock the starved thread isn't reacting to others; it simply **never gets its turn**. ## The two resources a thread can be starved of ### A) Starved of a lock A **lock** lets one thread at a time into a **critical section** (code touching shared data). When several threads contend for the same lock, the rule deciding *who gets it next* matters: - **Unfair / barging (the Java default).** Both `synchronized` and `new ReentrantLock()` are **unfair**: when a lock is released, a thread that happens to be running *right now* may grab it immediately — 'barging' ahead of threads that have been **queued and waiting longer**. Barging is great for **throughput** (no need to wake a parked waiter and context-switch to it), but a steady stream of barging threads can mean a particular waiter is **never** chosen → starvation. - **Fair (FIFO).** `new ReentrantLock(true)` is **fair**: the lock is always granted to the **longest-waiting** thread, like a queue at a counter. This *guarantees* no waiter is starved, but costs throughput because it forbids barging and forces more context switches. `ReentrantReadWriteLock(true)` and `Semaphore(permits, true)` also offer fair modes. `synchronized` has **no** fairness option — if you need fairness you must use the explicit `Lock` types. A second lock-starvation cause is a **greedy holder**: one thread keeps the lock for very long periods (or reacquires a **reentrant** lock so often it rarely lets go), so others rarely see it free. The cure is to **keep critical sections short**. ### B) Starved of the CPU Even with no locks, a **runnable** thread needs the **scheduler** to assign it a CPU core. Each Java thread has a **priority** (`Thread.setPriority`, 1–10, default 5). Higher priority *suggests* the scheduler should prefer it. On platforms that honor priorities strictly, a constant supply of high-priority work can leave low-priority threads **never scheduled** → CPU starvation. Crucially, **Java thread priorities are advisory and platform-dependent**: the JVM maps its 10 levels onto whatever the OS provides, and some OSes collapse or ignore them. So you must **never rely on priorities for correctness** — treat them as a hint, not a guarantee, and never assume a high-priority thread will always preempt a low one (nor that a low one will eventually run). ## Diagnosing starvation A starved thread shows **low CPU for itself** (it isn't running) while the system overall may be busy. Thread dumps repeatedly show it parked/waiting on the same monitor or in RUNNABLE-but-not-progressing state. This contrasts with deadlock (threads BLOCKED in a stable cycle) and livelock (high CPU, churning). ## How to prevent it 1. **Use a fair lock** (`new ReentrantLock(true)`, fair `ReentrantReadWriteLock`, fair `Semaphore`) when no waiter may be left behind — accept the throughput hit knowingly. 2. **Keep critical sections short** so the lock is frequently free and no holder is greedy. 3. **Don't depend on priorities** for correctness; if you must coordinate, use explicit synchronization, not scheduling hints. 4. **Distribute work fairly** with bounded queues / `ExecutorService` / concurrent collections rather than ad-hoc contention. ## How to derive the answer Ask: *which resource is denied — a lock or the CPU?* For a lock, the lever is **fairness** (FIFO vs barging) and **hold time**. For the CPU, the lever is the **scheduler** and **priorities**, which in Java are only advisory. Fairness trades throughput for a no-starvation guarantee — that trade-off is the heart of the topic.
- Why is a fair lock often slower than the default unfair one?Fairness forbids barging: it must hand the lock to the longest-waiting (parked) thread, which requires waking and context-switching to it instead of letting a currently-running thread reacquire it cheaply. That extra wakeup/switch overhead lowers throughput.
- Can you make a synchronized block fair?No. The synchronized monitor offers no fairness guarantee or knob. If you need fairness, switch to an explicit ReentrantLock constructed with new ReentrantLock(true) (or a fair ReadWriteLock/Semaphore).
saying these in an interview costs you the question
- Saying synchronized can be made fair — it has no fairness setting; use ReentrantLock(true)
- Treating thread priorities as a hard guarantee of scheduling order
- Claiming fair locks are always better — they reduce throughput vs barging
- Confusing starvation with deadlock (no cycle here) or assuming it pins the CPU (the victim shows low CPU)