Understanding Java synchronized Keyword, Monitors, and Lock Mechanisms
This article explains the Java synchronized keyword, its three purposes, usage forms for instance methods, static methods, and code blocks, and delves into the underlying monitor mechanism, object header structure, lock states, lock escalation, and related JVM optimizations with illustrative code examples.
The synchronized keyword is a common tool for thread synchronization in Java concurrency programming. It provides three main functions: mutual exclusion, visibility of shared variable changes, and ordering (although it does not prevent reordering).
Three usage forms of synchronized :
Annotating instance methods
Annotating static methods
Annotating code blocks
1. Annotating Instance Methods
When synchronized precedes an instance method, the lock defaults to the this object.
public class Thread1 implements Runnable {
// shared resource (critical resource)
static int i = 0;
public synchronized void increase() {
i++;
}
public void run() {
for (int j = 0; j < 10000; j++) {
increase();
}
}
public static void main(String[] args) throws InterruptedException {
Thread1 t = new Thread1();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
t2.start();
t1.join(); // main thread waits for t1
t2.join(); // main thread waits for t2
System.out.println(i);
}
}2. Annotating Static Methods
When applied to a static method, the lock is on the Class object.
public class Thread1 {
static int i = 0;
public static synchronized void increase() {
i++;
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int j = 0; j < 10000; j++) {
increase();
}
});
Thread t2 = new Thread(() -> {
for (int j = 0; j < 10000; j++) {
increase();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}3. Annotating Code Blocks
Using synchronized inside a method limits the lock scope to a specific block and can lock on any object.
public class Thread1 implements Runnable {
static int i = 0;
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
synchronized (String.class) {
i++;
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread1 t = new Thread1();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}Summary of behavior:
For synchronized instance methods, only one thread can enter at a time, acquiring the object's intrinsic lock; other threads block but can still execute other methods.
For synchronized static methods, only one thread can enter, acquiring the class lock.
For synchronized blocks, only one thread can enter the block, acquiring the lock of the specified object or class.
Each class has a class lock, and each object has an intrinsic lock; they are independent, allowing a thread to hold both simultaneously.
Monitor (Lock) Fundamentals
A monitor (also called a synchronization monitor) manages shared variables and the operations on them, ensuring that only one thread can access the protected section at a time.
Object Header Structure
In the JVM heap, each object consists of three parts: padding for alignment, instance fields, and the object header. The header contains two key components:
Klass Pointer : points to the class metadata.
Mark Word : stores runtime data such as hash code, GC age, lock state, and lock pointer.
The lock state is encoded in the Mark Word. Depending on contention, the lock can be in one of four states: no‑lock, biased lock, lightweight lock, or heavyweight lock.
Bytecode Perspective
For synchronized methods, the JVM sets the ACC_SYNCHRONIZED flag. For synchronized blocks, the compiler inserts monitorenter and monitorexit bytecode instructions.
public synchronized void doSth() {
System.out.println("Hello World");
}
public void doSth1() {
synchronized (SynchronizedTest.class) {
System.out.println("Hello World");
}
}Decompiling shows the ACC_SYNCHRONIZED flag for the method and explicit monitorenter / monitorexit for the block.
Lock Escalation Process
The JVM upgrades locks based on contention:
Biased Lock : Optimized for the common case where a single thread repeatedly acquires the same lock.
Lightweight Lock : Used when a few threads contend briefly; threads spin on CAS before blocking.
Heavyweight Lock : When contention is high or spinning exceeds a threshold, the lock inflates to a monitor that blocks threads via OS mutexes.
Lock elimination is another JVM optimization where the JIT removes locks that are proven to be uncontended.
Lock Types Comparison
Lock State
Advantages
Disadvantages
Suitable Scenarios
Biased Lock
Near‑zero lock/unlock overhead
Costly when many threads compete
Low contention, same thread repeatedly acquires
Lightweight Lock
Threads spin instead of blocking, fast response
CPU waste if spin lasts too long
Few threads, short lock hold time
Heavyweight Lock
No CPU spin, threads block
Higher latency due to OS context switch
High contention, long lock hold time
In summary, synchronized in Java relies on the monitor (object header) to provide mutual exclusion, visibility, and ordering, with the JVM dynamically adjusting the lock implementation (biased, lightweight, heavyweight) to balance performance and safety.
Full-Stack Internet Architecture
Introducing full-stack Internet architecture technologies centered on Java
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.