Reentrant Monitor in Java

Naveen AnamaneniUpdated · 3 min read
mediumJava 25javasynchronization

Java's intrinsic locks are reentrant. This means a thread that already holds a lock can enter it again, for example when one synchronized method calls another synchronized method on the same object, or when a synchronized method calls itself recursively. The JVM keeps a hold count for each lock: it goes up each time the owning thread enters and down each time it leaves, and the lock is released only when the count returns to zero. Without reentrancy, a thread would block waiting for itself. In this lesson, you will see reentrancy in action and why it matters.

Example: One Synchronized Method Calling Another

Let's see an example where a synchronized method calls two other synchronized methods on the same object.

Reentrant.java▶ Try it
public class Reentrant {
    private int marks = 0;
 
    synchronized void addAndReport(String name, int points) {
        System.out.println("Holds lock in addAndReport: "
            + Thread.holdsLock(this));
        add(points);
        report(name);
    }
 
    synchronized void add(int points) {
        marks += points;
    }
 
    synchronized void report(String name) {
        System.out.println(name + " now has " + marks + " marks");
    }
 
    public static void main(String[] args) {
        new Reentrant().addAndReport("Navika", 91);
    }
}
Output
Holds lock in addAndReport: true
Navika now has 91 marks

Explanation: addAndReport takes the object's lock. Inside it, add and report also need the same lock. Because the lock is reentrant, the thread that already holds it simply enters again (the hold count rises to 2 and falls back to 1). No other thread is involved, so nothing waits. Thread.holdsLock(this) confirms the current thread owns the lock.

Example: Recursion and Inheritance

ReentrantMore.java▶ Try it
public class ReentrantMore {
    static class Parent {
        synchronized String greet() {
            return "Hello";
        }
    }
 
    static class Child extends Parent {
        @Override
        synchronized String greet() {
            return super.greet() + " from Advitha";
        }
    }
 
    synchronized int factorial(int n) {
        return n <= 1 ? 1 : n * factorial(n - 1);
    }
 
    public static void main(String[] args) {
        System.out.println(new Child().greet());
        System.out.println("5! = " + new ReentrantMore().factorial(5));
    }
}
Output
Hello from Advitha
5! = 120

Explanation: Child.greet holds the object's lock and calls super.greet(), which is synchronized on the same object, so it re-enters. factorial calls itself five levels deep, re-entering the lock each time. Without reentrancy, both would deadlock against themselves.

Common Mistakes

Assuming nested synchronized calls on the same object will deadlock. They don't; the lock is reentrant.

Assuming reentrancy covers different objects. Holding one object's lock doesn't give you another's.

Practice

  1. Easy: Print Thread.holdsLock(obj) inside and outside a synchronized (obj) block.
  2. Harder: Write a synchronized recursive method that prints the depth at each level, and confirm that a second thread calling it waits until the whole recursion has finished.

Quick check: Reentrant monitor

4 questions
1What does reentrant mean for a lock?
2When is a reentrant lock fully released?
3Can a synchronized method call super's synchronized method on the same object?
4Which method checks if the current thread holds a lock?

Key Takeaways

Was this tutorial helpful?

Lesson 407 of 482