skip to content

What does it mean that Java's intrinsic locks are reentrant, and what problem does reentrancy prevent?

level: middleimportance: should knowfreq 55%

answer

  1. Lock owned per-thread, not per-call
  2. Hold count: ++ on acquire, -- on exit, release at 0
  3. Prevents self-deadlock when synchronized calls synchronized
  4. Enables synchronized super calls
  5. Caveat: lengthens critical section / can hide re-entry bugs

basics

~20 s

Reentrant means a thread that already holds a lock can acquire the same lock again without blocking itself. This lets a synchronized method safely call another synchronized method on the same object. Without it, a thread could deadlock against a lock it already owns.

solid answer

~50 s

Intrinsic locks are acquired per-thread, not per-call. Reentrancy means that if a thread already holds an object's monitor, a nested attempt to acquire that same monitor succeeds immediately instead of blocking. The JVM tracks a hold count: each acquire increments it, each exit decrements it, and the lock is only actually released when the count returns to zero. This matters because synchronized code routinely calls other synchronized code on the same object — for example a synchronized method invoking another synchronized method, or an overriding method calling super. If locks were non-reentrant, the thread would block waiting for a lock it itself holds, deadlocking. Reentrancy makes synchronized composition natural. The trade-off is that holding the lock across nested calls can lengthen the critical section, and reentrancy can mask design issues where a callback re-enters locked code unexpectedly.

code

java · 9 lines
java
class Widget {
    synchronized void a() {
        // already holds 'this' (hold count 1)
        b();           // re-acquires 'this' (hold count 2) -- OK because reentrant
    }
    synchronized void b() {
        // count back to 1 on exit; 'this' released when a() also exits
    }
}

go deeper

for a junior

Knows that a synchronized method can call another synchronized method on the same object without getting stuck.

for a middle

Explains the per-thread hold count, release-at-zero, and that reentrancy prevents self-deadlock including in super calls.

for a senior

Recognizes the trade-offs — enlarged critical sections and reentrancy hiding accidental re-entry from callbacks — and accounts for them in design.

for a principal

Reasons about reentrancy across an architecture (callbacks/listeners re-entering locked code), and when non-reentrant or scoped locking would better expose invariants, plus mapping to ReentrantLock features.

## Per-thread, not per-call A lock can be modeled two ways: count acquisitions *per invocation* (non-reentrant) or *per thread* (reentrant). Java's intrinsic locks are **reentrant**: ownership is associated with a **thread**, and that thread may acquire the lock it already owns any number of times. ## The hold count The JVM keeps a *hold count* and an *owner thread* for each monitor: - First acquire by a thread: owner = that thread, count = 1. - Each further acquire **by the same thread**: count++ (no blocking). - Each exit from a synchronized region: count--. - When count reaches **0**: the lock is released and another thread may acquire it. A *different* thread attempting to acquire while the count is > 0 blocks, as usual. ## The problem it prevents Consider: ```java class Widget { synchronized void a() { b(); } // already holds this synchronized void b() { /* ... */ } // wants this again } ``` When a thread calls `a()`, it holds `this`. Inside, it calls `b()`, which also wants `this`. With a **non-reentrant** lock, the thread would wait for a lock that *it itself* is holding — an instant **self-deadlock**. Because intrinsic locks are reentrant, the nested acquisition just bumps the hold count and proceeds. The same applies to inheritance: ```java class Base { public synchronized void op() {} } class Sub extends Base { public synchronized void op() { super.op(); } // re-acquires this — fine because reentrant } ``` Without reentrancy, every `super.op()` from a synchronized override would deadlock. ## Why it's the right default Object-oriented code composes methods freely; demanding that synchronized methods never call other synchronized methods on the same object would be impractical. Reentrancy lets locking compose naturally, which is why both intrinsic locks and `ReentrantLock` (the explicit j.u.c equivalent) are reentrant. ## Caveats - **Longer critical sections.** Because the lock is held across the whole nested call chain, the critical section can grow larger than you intended, increasing contention. - **Surprising re-entry.** If locked code calls out to a callback that calls back into your locked code, reentrancy lets it through silently — sometimes hiding a re-entrancy bug that a non-reentrant lock would have surfaced. - **Reentrancy is per-monitor.** Re-acquiring a *different* object's lock is ordinary locking and can still deadlock against another thread. ## Summary Reentrancy = a thread can re-acquire a lock it already owns; the JVM counts holds and releases only at count zero. It exists so synchronized methods can call one another (including super calls) without self-deadlock.

  • What would happen without reentrancy when a synchronized method calls another synchronized method on the same object?
    The thread would try to acquire a lock it already holds and block forever — a self-deadlock. Reentrancy avoids this by counting holds per owning thread.
  • Is ReentrantLock from java.util.concurrent reentrant in the same way?
    Yes — as the name says, ReentrantLock is also reentrant with a per-thread hold count. It adds features intrinsic locks lack (tryLock, timeouts, fairness, multiple conditions) but the reentrancy semantics match.

saying these in an interview costs you the question

  • Saying a thread blocks when it re-enters a lock it already holds — reentrancy specifically prevents that.
  • Thinking the hold count is shared across threads — it tracks one owning thread.
  • Assuming reentrancy means other threads can also enter — only the owning thread re-enters; others still block.
  • Believing reentrancy prevents all deadlocks — it only prevents self-deadlock on the same monitor.

context