In our last lesson, we identified race conditions as a major threat to the correctness of concurrent programs and introduced the synchronized keyword as a primary defense. You saw how it enforces mutual exclusion, ensuring that a critical section of code is executed by only one thread at a time. This prevents the chaotic interleaving of operations that leads to lost updates and unpredictable results.
Today, we will peel back the curtain on the synchronized keyword to understand how the JVM implements this powerful feature. Our goal is to explain the underlying mechanism of monitors, also known as intrinsic locks, which are at the very heart of Java's synchronization model. By the end of this lesson, you'll understand not just what synchronized does, but how it works at the level of JVM bytecode and object memory layout.
The Intrinsic Lock: Every Object's Guardian
In Java, every object has a single, built-in lock associated with it. This is called an intrinsic lock or a monitor. This monitor is the mechanism that the synchronized keyword uses to control access. When a thread wants to execute a synchronized method or block, it must first acquire the monitor of the associated object. Only one thread can "own" a monitor at any given time.
The key to understanding synchronized is identifying which object's monitor is being used.
- Synchronized Instance Methods: The lock is acquired on the monitor of the instance itself (the
thisobject). - Synchronized Static Methods: The lock is acquired on the monitor of the
Classobject representing the class in which the method is declared. - Synchronized Blocks: The lock is acquired on the monitor of the object provided in the parentheses.
This distinction is crucial. Two threads can execute a synchronized static method and a synchronized instance method of the same class concurrently, because they are locking on two different objects (MyClass.class and an instance of MyClass, respectively).
The following video provides an excellent walkthrough of these different synchronization contexts.
Java Synchronized - The synchronized keyword in Java and Java synchronized blocks and methods
Jakob Jenkov clearly explains how the monitor object is determined for instance methods, static methods, and synchronized blocks.
Please watch the following segments to solidify your understanding of which object is being locked in each case: Synchronized methods vs. blocks: This part introduces both forms and explains that instance methods are synchronized on this. Instance synchronization effect: See how synchronizing on the same instance (this) coordinates access between threads. Static synchronization: Learn how static methods use the Class object as their monitor. Mixed synchronization: Understand that static and instance synchronized methods in the same class use different locks and do not block each other.
The Conceptual Model of a Java Monitor
Now that you know every object has a monitor, let's visualize how threads interact with it. A monitor can be conceptually understood as a building with only one room (the critical section) and a single key (the lock).

Let's break down the numbered steps in this diagram:
- Enter: A thread arrives and wants to execute a synchronized block. It enters the Entry Set, which is conceptually a queue of threads waiting for the lock to become available. These threads are in the
BLOCKEDstate. - Acquire: When the current owner releases the lock, the JVM's scheduler selects a thread from the Entry Set. This thread acquires the lock and becomes the Owner. It moves from
BLOCKEDtoRUNNABLEand begins executing the critical section. - Release: The owner thread can voluntarily release the lock by calling the
wait()method (e.g., waiting for a condition to become true). When it does, it enters the Wait Set and is in theWAITINGorTIMED_WAITINGstate. It is no longer competing for CPU time. - Re-acquire: A thread in the Wait Set is moved back to the Entry Set when another thread calls
notify()ornotifyAll()on the same monitor. It must then re-compete for the lock like any other thread in the Entry Set. - Release and Exit: Once the owner thread completes the synchronized block, it automatically releases the lock and exits. A new owner can now be selected from the Entry Set.
This model provides a robust framework for managing concurrent access. The most common path for simple synchronization is 1 -> 2 -> 5. The wait/notify mechanism (steps 3 and 4) is used for more complex thread coordination patterns, which we'll cover later.
From synchronized to Bytecode
Your interest in Java's inner workings naturally leads to the question: how does the compiler translate the synchronized keyword into something the JVM can execute? The implementation differs slightly for methods and blocks.
For a synchronized method, the compiler simply sets a flag called ACC_SYNCHRONIZED in the method's metadata within the .class file.

For a synchronized block, the compiler generates explicit bytecode instructions: monitorenter and monitorexit.
The monitorenter instruction is placed at the beginning of the block. It attempts to acquire the monitor for the specified object. The monitorexit instruction is placed at the end to release the lock.
But what if an exception is thrown inside the synchronized block? If the lock isn't released, the application could deadlock. The Java compiler handles this gracefully.
5 Things You Didn’t Know About Synchronization in Java and Scala
This article from Harness provides a concise, deep dive into the bytecode implementation of synchronized blocks.
Please read these two sections: Start at Fact #1 to understand the MonitorEnter and MonitorExit bytecode instructions. Continue with Fact #2. This is a crucial section that explains how the compiler ensures the lock is always released by generating an implicit try...catch block in the bytecode to call monitorexit even when an exception occurs.
This automatic lock release is a major advantage of synchronized—it's impossible to forget to release the lock.
The Cost of Locking and JVM Optimizations
Acquiring and releasing a "fat" or "heavyweight" lock, which involves the operating system to park and unpark threads, is a relatively expensive operation. To minimize this overhead, the HotSpot JVM employs a clever optimization strategy called lock escalation. The JVM assumes that most contention is non-existent or brief and only uses heavyweight locks as a last resort.
This is managed using a few bits in every object's header, specifically in a field called the mark word. The value of the mark word changes to reflect the lock's current state.
Java Synchronized Vs Reentrantlock | Concurrency Interview
This section from AlgoMaster's guide provides an excellent, detailed explanation of the object header and the lock escalation process.
Focus on the section "How synchronized Works Internally". Pay close attention to: The Object Header and Mark Word section and the table showing how the mark word's content changes with the lock state. The Lock Escalation subsection, reading through the descriptions of Biased Locking, Thin (Lightweight) Locking, and Fat (Heavyweight) Locking.
Let's summarize the escalation path:
- Unlocked: The initial state. The mark word stores the object's hash code.
- Biased Lock: When a thread T1 first acquires a lock, the JVM "biases" it towards T1 by storing T1's ID in the mark word. Subsequent lock acquisitions by T1 are nearly free—just a simple check of the thread ID. This optimizes for the common case where a lock is used by only one thread.
- Thin Lock: If another thread, T2, tries to acquire the biased lock, the bias is revoked. The lock escalates to a "thin lock". The JVM uses an atomic Compare-And-Swap (CAS) operation to point the mark word to a lock record on the thread's stack. This is still very fast as it doesn't involve the OS. If T2 fails the CAS (because T1 still holds the lock), it will "spin" (perform a tight loop) for a short time, hoping the lock is released quickly.
- Fat Lock: If contention continues and spinning fails, the lock finally inflates to a "fat lock". A full-fledged monitor object is created in the heap, and any threads that fail to acquire the lock are parked by the OS until they are notified, incurring the full cost of a context switch.
This escalation means that in the common low-contention scenarios, synchronized is highly efficient.
Reentrancy: Locking Without Deadlocking Yourself
What happens if a synchronized method calls another synchronized method on the same object?
public class ReentrantExample {
public synchronized void outer() {
System.out.println("In outer");
inner(); // Calls another synchronized method
}
public synchronized void inner() {
System.out.println("In inner");
}
}
If the lock wasn't reentrant, the thread holding the lock for outer() would block forever when trying to acquire the lock for inner(), because it's already holding it! This would be a self-deadlock.
Fortunately, Java's intrinsic locks are reentrant. A thread that already owns a monitor can re-acquire it without blocking. The monitor simply keeps a count of how many times the thread has entered a synchronized block protected by it. The lock is only truly released when the count returns to zero.
Java Synchronized - The synchronized keyword in Java and Java synchronized blocks and methods
Jakob Jenkov provides a clear, concise explanation of reentrancy.
Watch the segment on re-entrancy, which demonstrates this concept with a simple code example.
Conclusion
In this lesson, we moved from the "what" to the "how" of Java's synchronized keyword. You now have a much deeper model of how the JVM provides thread safety, connecting a simple language feature to complex underlying mechanics.
Key Takeaways:
- Every Java object has an intrinsic lock, or monitor, which is the foundation of the
synchronizedmechanism. - The monitor being locked depends on the context:
thisfor instance methods, the.classobject for static methods, and an explicit object for synchronized blocks. - The
synchronizedkeyword is translated by the compiler into either theACC_SYNCHRONIZEDmethod flag or themonitorenter/monitorexitbytecode instructions. The compiler ensures locks are released even if exceptions occur. - The JVM optimizes locking via lock escalation, starting with cheap biased and thin locks (using CAS) and only inflating to expensive fat (heavyweight) locks under high contention.
- The state of the lock is stored in the mark word of the object's header.
- Intrinsic locks are reentrant, allowing a thread to acquire a lock it already holds, which is essential for object-oriented designs.
In the previous lesson, we introduced synchronized as a robust way to manage concurrent access. Now you understand the sophisticated machinery that makes it both safe and performant. In our next lesson, we will explore explicit locks, specifically ReentrantLock, and compare their features and trade-offs against the intrinsic locks you've just mastered.