Understanding the Implementation and Optimization of Java synchronized
This article explains how Java's synchronized keyword works internally, covering its implementation via monitorenter/monitorexit, the role of object headers and monitors, and the various lock optimizations introduced in JDK 1.6 such as spin locks, biased locks, lightweight locks, lock elimination, and lock coarsening.
When beginners first encounter multithreading in Java, they often rely on the synchronized keyword as a simple solution, but it is traditionally considered a heavyweight lock; JDK 1.6 introduced many optimizations that significantly improve its performance.
Implementation principle : synchronized guarantees that only one thread can execute a method or block at a time and provides memory‑visibility for shared variables. Any Java object can serve as a lock, which leads to three typical usages: (1) ordinary synchronized instance method – lock is the current object; (2) static synchronized method – lock is the Class object; (3) synchronized block – lock is the object specified in the parentheses.
Example code:
public class SynchronizedTest {
public synchronized void test1(){
// ...
}
public void test2(){
synchronized (this){
// ...
}
}
}Using the javap tool on the compiled class shows that a synchronized block is compiled into monitorenter and monitorexit bytecode instructions, while a synchronized method carries the ACC_SYNCHRONIZED flag in its method descriptor.
Synchronized block : the JVM inserts monitorenter at the start of the block and monitorexit at the end. Every object has an associated monitor; when a thread executes monitorenter it attempts to acquire the monitor’s ownership.
Synchronized method : the method is marked with the ACC_SYNCHRONIZED flag, and the lock object is either the instance (for non‑static methods) or the Class object (for static methods).
To understand how the lock is stored, we need to look at the Java object header and the monitor. The object header consists of two parts: the Mark Word and the Klass Pointer. The Mark Word holds runtime data such as hash code, GC age, lock state, owning thread ID, bias timestamp, etc. Its layout changes according to the lock state (no‑lock, biased, lightweight, heavyweight).
The monitor is a native data structure linked from the Mark Word. Its fields include Owner (the thread that currently holds the lock), EntryQ (a semaphore for threads waiting on the monitor), RcThis (the count of blocked/waiting threads), Nest (re‑entrancy count), HashCode (copied from the Mark Word), and Candidate (used to decide which thread to wake up).
Lock optimizations in JDK 1.6 introduce several techniques to reduce the overhead of synchronization:
Spin lock – a thread repeatedly checks the lock for a short period instead of blocking immediately.
Adaptive spin lock – the number of spin iterations is adjusted dynamically based on past success or failure.
Lock elimination – the JVM removes locks that are proven unnecessary through escape analysis.
Lock coarsening – consecutive lock/unlock pairs are merged into a larger lock region.
Biased lock – eliminates CAS operations when a lock is always acquired by the same thread.
Lightweight lock – uses CAS to acquire a lock without involving the OS mutex, suitable when contention is low.
Heavyweight lock – falls back to an OS‑level mutex when contention becomes high.
Spin lock : useful when the expected wait time is very short; the thread performs a busy‑wait loop instead of a context switch, but it can waste CPU cycles if the lock is held longer.
Adaptive spin lock : the JVM records the outcome of previous spins and adjusts the spin count; successful spins increase the count, while frequent failures reduce it.
Lock elimination : if the JVM determines that an object does not escape the current method (e.g., a local Vector used only inside the method), it can remove the internal synchronization of that object.
Example of lock elimination:
public void vectorTest(){
Vector<String> vector = new Vector<>();
for(int i = 0; i < 10; i++){
vector.add(i + "");
}
System.out.println(vector);
}Lock coarsening : when the JVM sees a series of short lock/unlock operations on the same object, it may expand them into a single larger lock region, reducing the number of acquisitions.
Lightweight lock acquisition involves three steps: (1) check if the object is in the unlocked state and create a lock record on the thread stack; (2) use CAS to replace the Mark Word with a pointer to the lock record; (3) if CAS fails or the Mark Word points to another thread, the lock inflates to a heavyweight lock.
Release of a lightweight lock also uses CAS to restore the original Mark Word.
Biased lock works by recording the owning thread ID in the Mark Word; as long as no other thread competes, the lock is acquired without any CAS. When contention appears, the biased lock is revoked at a safepoint and upgraded to a lightweight or heavyweight lock.
Heavyweight lock relies on the operating system's mutex; entering such a lock incurs a user‑mode to kernel‑mode transition, which is costly.
References: Zhou Zhimin, "深入理解 Java 虚拟机"; Fang Tengfei, "Java 并发编程的艺术"; online article "Java 中 synchronized 的实现原理与应用".
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Qunar Tech Salon
Qunar Tech Salon is a learning and exchange platform for Qunar engineers and industry peers. We share cutting-edge technology trends and topics, providing a free platform for mid-to-senior technical professionals to exchange and learn.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
