skip to content

Why is it dangerous that StampedLock is non-reentrant, and what bug does this commonly cause?

level: seniorimportance: should knowfreq 38%

answer

  1. Reentrant = same thread can re-lock; StampedLock isn't
  2. Re-acquire by holder → waits for itself → self-deadlock
  3. Hits recursion, callbacks, refactored helpers
  4. Keep sections flat; don't call out while locked
  5. Need re-entry? use ReentrantReadWriteLock

basics

~20 s

Non-reentrant means a thread that already holds the lock cannot acquire it again — if it tries, it blocks waiting for itself and deadlocks. So a method holding the write lock that calls another method which also takes the lock will freeze.

solid answer

~40 s

Reentrancy is the property that a thread already holding a lock can re-acquire it without blocking; synchronized and ReentrantReadWriteLock are reentrant. StampedLock is deliberately not — it tracks no per-thread hold count. The danger: if a thread holding the write (or read) lock calls another method, directly or indirectly, that tries to acquire the same StampedLock, the second acquire waits for the lock to be free, which will never happen because the same thread is the one holding it. The thread deadlocks against itself. This bites with recursion, with overridable/callback methods invoked while locked, and when refactoring splits a locked operation into helper methods that each lock. Mitigations: keep critical sections flat and self-contained (never call out while holding the lock), or use ReentrantReadWriteLock if re-entry is genuinely needed.

go deeper

for a junior

Knows 'reentrant' means a thread can lock something it already holds, and that StampedLock cannot.

for a middle

Can describe the self-deadlock that results when a thread re-acquires a StampedLock it holds, and names recursion as a trigger.

for a senior

Explains why it's non-reentrant (no hold count, performance), the call-out/refactor triggers, and the mitigations including choosing ReentrantReadWriteLock when re-entry is needed.

for a principal

Sets team guidelines (no call-outs under StampedLock, flat critical sections), reasons about the broader lock-discipline risks of an owner-less lock, and chooses the lock primitive per access pattern.

### What 'reentrant' means A lock is **reentrant** (also called *recursive*) if a thread that **already holds** it can **acquire it again** without blocking, and must then release it the same number of times before it is truly free. Java's `synchronized` is reentrant: a synchronized method can call another synchronized method on the same object on the same thread and it just works. `ReentrantLock` and `ReentrantReadWriteLock` are reentrant by design (the name says so) — internally they keep a *hold count* per owning thread. ### What StampedLock does instead `StampedLock` keeps **no per-thread ownership or hold count**. A stamp is just a version token, not "thread X owns this." Consequently, the lock has **no idea** that the thread now requesting it is the same thread that already holds it. From the lock's point of view, a second acquire from an already-holding thread looks exactly like a *different* thread wanting in — so it makes that request **wait** until the lock is released. But the only thread that can release it is the very thread now blocked waiting. Nobody will ever release it. The thread is **deadlocked against itself** — a *self-deadlock*. ### The classic bug shapes 1. **Recursion under the lock.** ```java void process(Node n) { long stamp = lock.writeLock(); try { // ... mutate n ... if (n.child != null) process(n.child); // re-acquires writeLock -> SELF-DEADLOCK } finally { lock.unlockWrite(stamp); } } ``` The recursive call tries to take the write lock again and hangs forever. 2. **Calling out to code that re-locks.** A method holding the lock calls a helper, a listener, or an overridden method that (perhaps far down the call chain) acquires the same StampedLock. With a reentrant lock this is merely *inadvisable* (you can call out while locked and risk other deadlocks); with a non-reentrant lock it is an **immediate** self-deadlock if that path re-acquires. 3. **Refactoring that splits a locked block.** Extracting part of a critical section into a helper that "also locks to be safe" introduces a second acquire on the same thread. ### Why StampedLock chose non-reentrancy Tracking per-thread hold counts costs memory and CPU, and the optimistic-read design and mode conversions don't map cleanly onto reentrant semantics. Dropping reentrancy keeps StampedLock lean and fast — its whole reason to exist. The trade is that **the programmer must guarantee a thread never tries to re-enter**. ### How to avoid the bug - **Keep critical sections flat and short.** Acquire, do the minimal work, release. Do not call arbitrary or recursive code while holding a StampedLock. - **Never call out (callbacks, virtual methods, listeners) while holding the lock.** Compute under the lock, release, then call out with the result. - **If you genuinely need re-entry** (recursive data-structure traversal under one lock, etc.), use **`ReentrantReadWriteLock`** or `synchronized` instead — pick the right tool. - In code review, treat "locks a StampedLock and then calls a method" as a smell to inspect. ### Related gotchas Because it tracks no owner, StampedLock also won't catch the mistake of one thread unlocking with a stamp another thread produced — correctness depends entirely on disciplined stamp handling. And it has **no `Condition`** support, so wait/await coordination must use other primitives.

  • Why did the JDK authors make StampedLock non-reentrant on purpose?
    Reentrancy requires tracking per-thread ownership and hold counts, which adds memory and CPU overhead and complicates the optimistic-read and mode-conversion design. Dropping it keeps StampedLock minimal and fast — its raison d'être — at the cost of putting the no-re-entry burden on the developer.
  • How would you fix code that recursively traverses a tree while holding a StampedLock write lock?
    Either acquire the lock once at the top level and have the recursive helper assume it is already held (passing the protected state, not re-locking), or switch to ReentrantReadWriteLock / synchronized which permit re-entry. The key is exactly one acquire per thread for the whole operation.

saying these in an interview costs you the question

  • Believing a thread can safely re-acquire a StampedLock it already holds
  • Calling recursive or overridable methods while holding a StampedLock
  • Assuming StampedLock can replace ReentrantReadWriteLock unchanged in reentrant code
  • Expecting the lock to detect or report the self-deadlock (it just hangs)

context