What is the difference between synchronized method and synchronized block?
Both options provide synchronization, but they differ in what exactly is locked and which object is used as the lock.
Junior Level
Basic Understanding
Both options provide synchronization, but they differ in what exactly is locked and which object is used as the lock.
synchronized method
public class Counter {
private int count = 0;
// Locks the ENTIRE method
// Lock object: this (current instance)
public synchronized void increment() {
count++;
}
// Static method
// Lock object: Counter.class (class object)
public static synchronized void resetAll() {
// ...
}
}
synchronized block
public class Counter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
// Locks ONLY this block
// Lock object: any specified object
synchronized(lock) {
count++;
}
}
}
Comparison Table
| Characteristic | synchronized method | synchronized block |
|---|---|---|
| Lock object | Always this (or Class) |
Any object |
| Lock scope | Entire method | Only the specified block |
| Readability | Higher (declarative) | Lower (more nesting) |
| Flexibility | Low | High |
| Safety | Lower (any external code can acquire this and block the method) |
Higher (private lock object is inaccessible from outside, less deadlock risk) |
When to use which
- Method — when the entire method must be atomic and there’s no issue with external access to
this - Block — when only part of the method needs synchronization, or you need control over the lock object
Middle Level
Bytecode-Level Differences
synchronized block
Uses explicit monitorenter and monitorexit instructions:
public void increment() {
synchronized(lock) {
count++;
}
}
Bytecode:
public void increment();
Code:
0: aload_0
1: getfield #2 // Field lock:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter // ← ACQUIRE
7: aload_0
8: dup
9: getfield #3 // Field count:I
12: iconst_1
13: iadd
14: putfield #3 // Field count:I
17: aload_1
18: monitorexit // ← RELEASE (normal exit)
19: goto 27
22: astore_2
23: aload_1
24: monitorexit // ← RELEASE (on exception)
25: aload_2
26: athrow
27: return
synchronized method
Uses the ACC_SYNCHRONIZED flag in method attributes:
public synchronized void increment() {
count++;
}
Bytecode (note — NO monitorenter/monitorexit!):
public synchronized void increment();
flags: ACC_PUBLIC, ACC_SYNCHRONIZED // ← Synchronization flag
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #2 // Field count:I
5: iconst_1
6: iadd
7: putfield #2 // Field count:I
10: return
The JVM automatically acquires and releases the monitor when calling a method with this flag.
ACC_SYNCHRONIZED — a flag in bytecode (ACC = access flags). The JVM sees this flag and automatically wraps the method call in monitor enter/exit.
Granularity (Scope)
// BAD: synchronized method locks the entire method
public synchronized void processOrder(Order order) {
validate(order); // Doesn't require locking
calculatePrice(order); // Doesn't require locking
updateInventory(order); // Requires locking
sendNotification(order);// Doesn't require locking (I/O operation!)
}
// GOOD: synchronized block only for the critical section
public void processOrder(Order order) {
validate(order); // Without lock
calculatePrice(order); // Without lock
synchronized(inventoryLock) {
updateInventory(order); // Only this is locked
}
sendNotification(order); // Without lock
}
Private Locks
public class SafeCounter {
// BAD: someone outside could do synchronized(counter)
public synchronized void increment() {
count++;
}
// GOOD: private lock — no one outside can acquire it
private final Object lock = new Object();
public void increment() {
synchronized(lock) {
count++;
}
}
}
Why private lock is better:
- Encapsulation — external code cannot block your object
- Deadlock prevention — external code cannot create a deadlock with your lock
- Starvation prevention — external code cannot starve your methods
- Multiple locks — you can have different locks for different resources
Multiple Locks in One Class
public class UserProfileCache {
private final Map<Integer, Profile> profiles = new HashMap<>();
private final Map<Integer, Avatar> avatars = new HashMap<>();
// Separate locks for independent resources
private final Object profilesLock = new Object();
private final Object avatarsLock = new Object();
public Profile getProfile(int id) {
synchronized(profilesLock) {
return profiles.get(id);
}
}
public Avatar getAvatar(int id) {
synchronized(avatarsLock) {
return avatars.get(id);
}
}
// Both locks simultaneously
public void clearAll() {
synchronized(profilesLock) {
synchronized(avatarsLock) {
profiles.clear();
avatars.clear();
}
}
}
}
When synchronized method is a reasonable choice
- Small methods — the entire method needs to be atomic
- Inner classes — no external access to
this - No contention — only one thread accesses the object
When synchronized block is better: when you need to lock only part of the method, or when you need a separate lock object.
Senior Level
Under the Hood: ACC_SYNCHRONIZED Flag
A synchronized method does NOT contain monitorenter/monitorexit instructions in bytecode. Instead, the JVM checks the flag on method call:
// JVM pseudocode on method call:
if (method.hasFlag(ACC_SYNCHRONIZED)) {
monitor.enter(method.isStatic() ? clazz : receiver);
try {
invokeMethod();
} finally {
monitor.exit(method.isStatic() ? clazz : receiver);
}
}
This works slightly faster (difference ~0-5%, not noticeable in practice), as it doesn’t require additional instructions in the method body.
JIT Compiler Optimizations
Lock Coarsening
// Before optimization:
for (int i = 0; i < items.size(); i++) {
synchronized(lock) {
total += items.get(i).price;
}
}
// After Lock Coarsening:
synchronized(lock) {
for (int i = 0; i < items.size(); i++) {
total += items.get(i).price;
}
}
JIT merges many locks on the same object into one big lock.
Important: This only works if the JIT can prove that inside the loop there are no points where another thread could see an intermediate state.
Lock Elision
public void process() {
// This object does NOT escape the method
StringBuilder sb = new StringBuilder();
synchronized(sb) { // JIT will remove this lock!
sb.append("data");
}
}
Escape Analysis determines:
- GlobalEscape — object is returned or stored in a field → lock NOT removed
- ArgEscape — object passed as argument but not stored → lock may be removed
- NoEscape — object is fully local → lock removed
For synchronized(this), this works less often, since this almost always “escapes”.
Comparison Table (Advanced)
| Characteristic | synchronized method | synchronized block |
|---|---|---|
| Bytecode | ACC_SYNCHRONIZED flag |
monitorenter/exit instructions |
| Lock object | Always this or Class |
Any object (prefer private final) |
| Reliability | Lower (public lock) | Higher (encapsulated lock) |
| Lock Elision | Works rarely | Works often |
| Granularity | Entire method | Only critical section |
| I/O inside | Blocks during I/O | Can move I/O outside the lock |
Performance and Highload
Avoid Static Synchronized
// VERY BAD: class-level lock
public class Config {
private static Map<String, String> settings = new HashMap<>();
public static synchronized void set(String key, String value) {
settings.put(key, value); // Blocks EVERYONE using Config.class
}
}
This is the most dangerous kind of synchronization — it can paralyze the entire system.
Solution:
public class Config {
private static final Map<String, String> settings = new ConcurrentHashMap<>();
private static final Object settingsLock = new Object();
public static void set(String key, String value) {
settings.put(key, value); // ConcurrentHashMap — lock-free
}
}
Performance Impact
| Operation | Time | Note |
|---|---|---|
| Method (uncontended) | ~10-20 ns | ACC_SYNCHRONIZED overhead |
| Block (uncontended) | ~10-20 ns | monitorenter/exit |
| Method (contended) | ~1000+ ns | Context switch |
| Block (contended) | ~1000+ ns | Context switch |
| Block (private lock) | ~10-20 ns | Less contention |
Diagnostics
javap -v
javap -v MyClass.class
For a method:
public synchronized void method();
descriptor: ()V
flags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZED // ← Flag here
For a block:
public void method();
Code:
...
monitorenter // ← Instruction here
...
monitorexit // ← And here
Thread Dumps
"thread-1" BLOCKED on object monitor
- waiting to lock <0x000000076af0c8d0> (a com.example.MyClass)
// For method: shows it's this (MyClass)
// For block: shows the specific lock object
Best Practices
- Prefer synchronized block — more control
- Use private final Object lock — encapsulation
- Minimize lock scope — don’t block I/O
- Avoid synchronized(this) — someone outside could block
- Avoid static synchronized — class-level locking is dangerous
- Consider ConcurrentHashMap — often better than synchronized HashMap
- Monitor contention — if growing, switch to segmented locks
Interview Cheat Sheet
Must know:
- synchronized method uses ACC_SYNCHRONIZED flag, block uses monitorenter/monitorexit instructions
- synchronized method locks the entire method on
this(orClassfor static); block locks only the specified part on any object - Private lock (
private final Object lock) is safer: external code cannot acquire your lock and create a deadlock - JIT applies Lock Elision more often to synchronized blocks (local object) than to synchronized(this)
- Granularity: block allows moving I/O and long operations outside synchronization
- Uncontended performance is the same (~10-20 ns), the difference is in flexibility and safety
Frequent follow-up questions:
- Why is synchronized(this) dangerous? — Any external code can do
synchronized(obj)and block your methods - Can you have multiple locks in one class? — Yes, different private final Object for different resources (profiles, avatars, etc.)
- When is synchronized method a reasonable choice? — Small methods, entire method is atomic, no external access to
this - Why is static synchronized dangerous? — Class-level lock (
ClassName.class) — one lock for ALL instances
Red flags (do NOT say):
- “Synchronized method is faster than block” — no, performance is the same when uncontended
- “Synchronized(this) is safe if the class is internal” — generally no, encapsulation is broken
- “Monitorenter exists in synchronized method” — no, there’s the ACC_SYNCHRONIZED flag
- “Private lock is only for aesthetics” — no, it prevents external deadlock and starvation
Related topics:
- [[4. What is monitor in Java]] — intrinsic monitor mechanism
- [[5. How does synchronized work at monitor level]] — lock state evolution
- [[7. What is reentrant lock]] — ReentrantLock as a more flexible alternative
- [[1. What is the difference between synchronized and volatile]] — when synchronization is needed at all