skip to content

What is the fairness option on ReentrantLock, and what is the trade-off of enabling it?

level: seniorimportance: should knowfreq 48%

answer

  1. Fair = FIFO grant order; unfair (default) = barging allowed
  2. Unfair = higher throughput; fair = no starvation but slower
  3. Barging avoids a context switch when a running thread can take a just-freed lock
  4. no-arg tryLock() always barges, even on a fair lock
  5. synchronized is always unfair; default to unfair, measure before going fair

basics

~20 s

A fair ReentrantLock (new ReentrantLock(true)) hands the lock to waiting threads in the order they asked for it, so no thread is starved. The default unfair lock lets whoever grabs it first win, which is faster overall but can leave some threads waiting longer.

solid answer

~50 s

ReentrantLock has an optional fairness policy set in its constructor. The default, new ReentrantLock() (unfair), allows barging: when the lock is released, a thread requesting it right then can win even if others have been queued longer. A fair lock, new ReentrantLock(true), grants the lock in first-come-first-served (FIFO) order of the wait queue. Fairness prevents starvation — no thread waits indefinitely while latecomers jump ahead — but it costs throughput, often substantially, because it disables barging: a thread that could have run immediately must instead hand off to the queued head, adding context switches. The standard guidance is to keep the default unfair lock unless you have measured starvation or a hard latency-fairness requirement; in practice unfair locks deliver far higher throughput and starvation is rare. Note the no-arg tryLock() always barges and ignores fairness.

go deeper

for a junior

Knows new ReentrantLock(true) is fair (FIFO) and the default is unfair, and that fairness relates to who gets the lock next.

for a middle

States the fair-vs-unfair trade-off (no starvation vs higher throughput) and that the default is unfair.

for a senior

Explains the barging mechanism and context-switch cost, recommends defaulting to unfair and measuring before enabling fairness, and knows tryLock barges.

for a principal

Quantifies the trade-off relative to lock hold time and contention, sets policy on when fairness SLAs justify the throughput hit, and considers whether a different synchronizer or lock-free design avoids the dilemma entirely.

## What fairness means When several **threads** are waiting for a lock and the holder releases it, *which* waiter gets it next? Two policies: - **Fair** — grant the lock in **FIFO** (first-in, first-out) order: the thread that has been waiting longest goes next. No thread can be perpetually overtaken. - **Unfair (barging)** — when the lock frees, any thread requesting it *at that moment* may grab it, even if others have been queued longer. The newcomer 'barges' past the queue. `ReentrantLock` lets you choose at construction: ```java Lock unfair = new ReentrantLock(); // default — unfair, barging allowed Lock fair = new ReentrantLock(true); // fair — strict FIFO ``` ## Why unfair is the default — throughput Barging is faster. Consider the moment a lock is released while a waiting thread sits at the head of the queue, not yet scheduled onto a CPU. If a *running* thread requests the lock right then, an unfair lock just hands it over and that thread proceeds immediately. A fair lock instead must wait for the queued thread to be woken and scheduled — a **context switch** that costs time during which the lock sits idle. Across millions of acquisitions this overhead is large, so unfair locks typically achieve **much higher throughput**. Crucially, even unfair locks rarely starve threads in practice because each thread does eventually get scheduled and win some race. ## Why you might still want fair — bounded waiting Unfair locks permit **starvation**: a particular thread could, in pathological scheduling, keep losing the race and wait far longer than others. If you need a *guarantee* that waiting time is bounded — say, a fairness SLA, or a small number of long-held lock acquisitions where one stalled thread is unacceptable — a fair lock provides it. Fairness matters most when **lock hold times are long** (so the per-handoff context switch is negligible relative to the work) and **predictable latency** beats raw throughput. ## The trade-off in one line **Fair = no starvation, lower throughput. Unfair = possible (rare) starvation, higher throughput.** ## Important caveats 1. **`tryLock()` (no-arg) always barges**, even on a fair lock — it grabs a free lock immediately regardless of the queue. If you want fair-honoring polling, use the timed form `tryLock(0, TimeUnit.SECONDS)`, which *does* respect fairness. 2. **Fairness is about acquisition order, not about preventing reordering or guaranteeing scheduling** — the OS scheduler still decides when a woken thread actually runs. 3. **`synchronized` is always unfair** — it has no fairness option; this is one more reason `ReentrantLock` exists. 4. Fairness applies to the lock's entry queue and to fair `Condition` signaling order on that lock. ## Practical guidance Use the **default unfair** lock. Switch to fair **only** when you have measured or can clearly reason about starvation, or have a hard requirement for FIFO/bounded waiting — and accept the throughput cost. Don't enable fairness 'to be safe'; it usually just makes the system slower with no benefit.

  • Why does a fair lock generally have lower throughput than the default unfair lock?
    A fair lock forbids barging: when the lock frees, it must hand off to the longest-waiting queued thread even if a currently-running thread could take it immediately. Waking and scheduling the queued thread incurs a context switch during which the lock sits idle. Multiplied over many acquisitions, that overhead reduces throughput; the unfair lock skips it by letting the ready thread barge in.
  • Does no-argument tryLock() respect a fair lock's queue?
    No. The barging tryLock() acquires the lock whenever it is free, ignoring the fairness ordering and any queued threads. To poll while honoring fairness, use the timed form tryLock(0, TimeUnit.SECONDS), which does respect the queue.

saying these in an interview costs you the question

  • Claiming fair locks are faster — they are typically slower due to forced handoffs/context switches
  • Enabling fairness by default 'to be safe' without measuring starvation
  • Thinking synchronized can be made fair — it can't
  • Believing no-arg tryLock() honors fairness — it always barges
  • Confusing fairness (acquisition order) with a scheduling guarantee about when a thread runs

context