🧵 Section 9 · Question #6

What is the difference between synchronized method and synchronized block

The primary distinction between a synchronized method and a synchronized block centers on lock target selection, synchronization granularity (scope), and bytecode execution mech...


🟢 Junior Level

30-Second Summary

The primary distinction between a synchronized method and a synchronized block centers on lock target selection, synchronization granularity (scope), and bytecode execution mechanics:

  1. Lock Target Selection:
    • In a synchronized method, the monitor object is predefined by language syntax: it is strictly this for instance methods or ClassName.class for static methods.
    • In a synchronized block, the developer explicitly designates any reference object as the monitor (the standard best practice is a private final Object lock = new Object();).
  2. Lock Scope (Granularity):
    • A synchronized method locks the entire body from invocation to return.
    • A synchronized block isolates strictly the critical section, leaving data validation, transformations, and I/O operations outside the lock.
  3. Bytecode Implementation:
    • A method is marked with the ACC_SYNCHRONIZED access flag in its class file metadata; the JVM manages monitor acquisition and release during stack frame creation and unwinding.
    • A block compiles into explicit bytecode instructions (monitorenter and monitorexit) paired with an implicit Exception table.

Comparison Matrix

Dimension synchronized Method synchronized Block
Lock Target Implicitly this or ClassName.class Any explicit reference object
Lock Scope Entire method body Arbitrary statement range within a method
Encapsulation ❌ Broken (this is exposed externally) ✅ Intact (when using private final Object lock)
Bytecode Mechanism ACC_SYNCHRONIZED method flag monitorenter / monitorexit bytecodes
Concurrency Flexibility Low (single monitor per instance) High (supports lock splitting / multiple monitors)
Deadlock Risk Higher (vulnerable to external client locking) Lower (internal state remains isolated)

Java 21 Code Examples

public class SynchronizationDemo {
    private final Object paymentLock = new Object();
    private final Object notificationLock = new Object();
    private int balance = 0;

    // 1. Synchronized Method: Locks the entire instance 'this'
    public synchronized void updateBalance(int delta) {
        this.balance += delta;
    }

    // 2. Synchronized Block: Granular lock isolating strictly the critical section
    public void processPayment(int amount, String userEmail) {
        // Step 1: Pre-processing & validation (Executed concurrently without locking)
        validateAmount(amount);

        // Step 2: Critical section protected by dedicated private monitor
        synchronized (paymentLock) {
            this.balance -= amount;
        }

        // Step 3: Blocking I/O or network call (Strictly OUTSIDE synchronized block!)
        sendNotification(userEmail);
    }

    // 3. Static Synchronized Method: Locks SynchronizationDemo.class
    public static synchronized void globalReset() {
        // Mutates class-level static state
    }

    private void validateAmount(int amount) { /* validation */ }
    private void sendNotification(String email) { /* external network call */ }
}

🟡 Middle Level

Bytecode Mechanics: ACC_SYNCHRONIZED vs monitorenter / monitorexit

The difference in execution inside HotSpot reflects how the bytecode is structured:

1. Method with synchronized Modifier:

The javac compiler emits no monitor bytecodes in the method’s code stream. Instead, it marks the method in the method_info structure:

public synchronized void deposit(int);
  descriptor: (I)V
  flags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZED
  Code:
     0: aload_0
     1: ...
     5: return

When invoked, the JVM method resolution logic checks ACC_SYNCHRONIZED. It acquires the monitor of this (or Class for static calls) before entering the frame and releases it upon method return or when an unhandled exception unwinds the stack.

2. Block with synchronized (lock):

A synchronized block translates into explicit bytecode instructions:

public void withdraw(int);
  Code:
     0: aload_0
     1: getfield      #7     // Field lock:Ljava/lang/Object;
     4: dup
     5: astore_2
     6: monitorenter         // Explicit monitor entry
     7: ...                  // Critical section instructions
    12: aload_2
    13: monitorexit          // Normal exit path release
    14: goto          22
    17: astore_3             // Exception handler catches any Throwable
    18: aload_2
    19: monitorexit          // Guaranteed release on exception
    20: aload_3
    21: athrow
    22: return
  Exception table:
     from    to  target type
         7    14    17   any
        17    20    17   any

Encapsulation Vulnerability of synchronized(this) & Private Lock Pattern

Using synchronized methods or synchronized(this) violates OOP encapsulation. The this reference is publicly accessible to any caller holding an object reference.

The Problem: External Interference & Accidental Deadlock

// Vulnerable library class:
public class OrderService {
    public synchronized void processOrder() {
        // Business logic
    }
}

// Rogue or faulty client code:
OrderService service = new OrderService();
synchronized (service) { // External code hijacks our instance monitor!
    Thread.sleep(60_000); // Freezes all OrderService synchronized methods application-wide!
}

The Idiom: Private Lock Object

Encapsulating the monitor inside a private reference guarantees that outside code cannot interfere:

public class SecureOrderService {
    // Hidden from reflection and external packages
    private final Object lock = new Object();

    public void processOrder() {
        synchronized (lock) {
            // Critical section is immune to external monitor hijacking
        }
    }
}

Fine-Grained Lock Splitting (Lock Striping)

Synchronized blocks allow different sections of an object’s state to be guarded by independent locks, preventing threads operating on disjoint fields from contending with one another:

public class MultiResourceRegistry {
    private final Object userLock = new Object();
    private final Object orderLock = new Object();

    private final List<String> users = new ArrayList<>();
    private final List<String> orders = new ArrayList<>();

    public void registerUser(String user) {
        synchronized (userLock) { // Threads modifying users...
            users.add(user);
        }
    }

    public void registerOrder(String order) {
        synchronized (orderLock) { // ...run concurrently with threads modifying orders!
            orders.add(order);
        }
    }
}

If both methods were synchronized, modifying users would unnecessarily block threads modifying orders.


🔴 Senior Level

Inheritance & Polymorphism Trap: Loss of ACC_SYNCHRONIZED

According to the Java Language Specification (JLS §8.4.3.6), the synchronized modifier is not part of a method’s formal signature and is never inherited by overriding subclasses!

public class BaseRepository {
    public synchronized void persistData() {
        // Thread-safe persistence logic
    }
}

public class ExtendedRepository extends BaseRepository {
    @Override
    public void persistData() {
        // ⚠️ CRITICAL HAZARD: persistData() in ExtendedRepository is NOT synchronized!
        // The ACC_SYNCHRONIZED flag is missing from the subclass method table.
        prepare();
        super.persistData(); // super call acquires 'this', but 'prepare()' was unsynchronized!
    }
}

If a developer relies on subclass polymorphism for thread-safety, an un-synchronized override introduces silent, hard-to-reproduce race conditions. Using a synchronized block around a private internal lock makes the concurrency control invariant visible and protected from override omission.


static synchronized Across ClassLoader Hierarchies

A static synchronized method locks on ClassName.class:

public class GlobalConfig {
    public static synchronized void update(String key, String value) { ... }
}

ClassLoader Isolation Pitfall:

  1. In Java, class identity is strictly governed by the pair: Fully Qualified Class Name + Defining ClassLoader.
  2. In modular runtimes, servlet containers (Tomcat), or plugin engines (OSGi), the same class may be loaded by two separate ClassLoaders (ClassLoader A and ClassLoader B).
  3. In memory, there exist two distinct java.lang.Class instances for GlobalConfig.
  4. Threads calling GlobalConfig.update() through different ClassLoaders acquire two completely different monitors, completely shattering the intended mutual exclusion!

JIT Compiler Escape Analysis & Lock Elision

The choice of lock target directly impacts HotSpot C2 compiler optimizations:

  1. synchronized(this) / Synchronized Method: The this instance is allocated outside the method frame and its reference is passed in as local variable 0. Escape Analysis almost always categorizes this as ArgEscape or GlobalEscape. Consequently, the JIT C2 compiler cannot elide the lock, even if the method is called in a local single-threaded context.
  2. synchronized(lock): When a lock is instantiated and confined strictly within a method scope (e.g., local buffering structures), Escape Analysis marks it as NoEscape. The JIT C2 compiler completely eliminates the monitorenter and monitorexit instructions from compiled machine code (Lock Elision), achieving zero runtime overhead.

4 Tricky Questions

1. Is the synchronized modifier inherited when a subclass overrides a synchronized method?

Answer: No, it is never inherited.

The synchronized keyword is an implementation detail of the method body, not part of the interface contract or method signature. When a subclass overrides a method declared as synchronized in a superclass, the subclass method is un-synchronized by default unless explicitly annotated with synchronized. Even if the subclass method delegates to super.method(), only the superclass execution portion is synchronized; any code preceding or following the super invocation remains unprotected against concurrent threads.


2. Can two threads concurrently execute synchronized static void methodA() and synchronized void methodB() on the same instance?

Answer: Yes, they execute fully in parallel without blocking each other.

The two methods synchronize on completely different monitor objects:

  • synchronized static void methodA() acquires the monitor of the Class object (MyClass.class).
  • synchronized void methodB() acquires the monitor of the specific instance (this).

Because their underlying intrinsic monitors are distinct, there is no mutual exclusion between them. If methodB() reads or writes static fields modified by methodA(), a severe Race Condition will occur.


3. Why is invoking network calls, database queries, or I/O operations inside a synchronized method considered a severe defect?

Answer: I/O latencies are non-deterministic, varying from milliseconds to tens of seconds during network degradation or database lock contention:

  1. Holding an intrinsic monitor during I/O forces all contending threads trying to enter any synchronized method on that object into the BLOCKED state.
  2. Contending threads accumulate in JVM queues (_cxq/_EntryList), consuming native thread resources and rapidly exhausting application thread pools (e.g., Tomcat HTTP request pools).
  3. This creates a system-wide cascade failure (Thread Pool Starvation).
    Rule: Synchronized sections must contain only swift in-memory mutations. All network and blocking I/O calls must be placed strictly outside synchronized blocks.

4. How can external client code initiate a Denial-of-Service (DoS) attack on a class using synchronized methods?

Answer: Because synchronized methods synchronize on this, any external code that holds a reference to the instance can directly synchronize on it:

synchronized (targetService) {
    Thread.sleep(Long.MAX_VALUE); // Hijacks the monitor permanently
}

As long as the external thread holds the monitor of targetService, no other thread in the application can execute any synchronized method on that instance. The entire service becomes completely unresponsive. Guarding critical sections using an encapsulated private final Object lock = new Object() prevents external callers from ever obtaining the monitor reference.


🎯 Interview Cheat Sheet

Core Concepts Matrix

| Characteristic | Synchronized Method | Synchronized Block | | :— | :— | :— | | Lock Object | Implicit (this or Class) | Explicit reference object | | Bytecode Tag | ACC_SYNCHRONIZED flag | monitorenter / monitorexit instructions | | Scope Flexibility| Coarse (entire method) | Fine (isolated statements) | | Encapsulation | Poor (exposed via this) | Excellent (via private final Object lock) | | I/O Safety | Dangerous (prone to holding locks across I/O) | Safe (I/O easily extracted outside block) |


Key Takeaways

  • Prefer Private Locks: Use private final Object lock = new Object() instead of synchronized(this) or synchronized methods to prevent external lock hijacking.
  • Inheritance Trap: synchronized is not inherited by overridden methods; thread safety must be re-applied explicitly in subclasses.
  • Separate Locks: Use lock splitting to protect independent mutable fields with distinct locks, maximizing throughput.
  • Never Hold Locks Across I/O: Restrict synchronization strictly to in-memory state manipulation.

Red Flags & Anti-Patterns to Avoid

  • ❌ Locking on new Object() inside a method: synchronized(new Object()) creates a new instance on every call, providing zero mutual exclusion across threads.
  • ❌ Assuming synchronized methods run faster: The execution overhead between ACC_SYNCHRONIZED and monitorenter is negligible compared to lock hold duration and thread contention.
  • ❌ Assuming static synchronized protects instance methods: Static and instance methods use different monitors (Class vs this) and run concurrently.
  • ❌ Reassigning non-final lock references: Lock objects must always be declared final; reassigning a lock reference breaks mutual exclusion immediately.