Reentrant Monitor in Java
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.
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);
}
}Holds lock in addAndReport: true
Navika now has 91 marksExplanation: 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
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));
}
}Hello from Advitha
5! = 120Explanation: 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
- Easy: Print
Thread.holdsLock(obj)inside and outside asynchronized (obj)block. - 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.
Key Takeaways
Was this tutorial helpful?