Back to Synchronization
Q6Question 6 of 8

Is the lock released if exception occurs in synchronized?

Key Concepts
  • Yes: when an exception is thrown inside a synchronized block, the JVM automatically releases the monitor lock.
  • The Java compiler emits two monitorexit instructions in the bytecode: one for the normal exit path and one for the exceptional exit path.
  • This is a major advantage of synchronized over ReentrantLock, where you must manually call unlock() in a finally block.
  • Forgetting to call unlock() on a ReentrantLock after an exception causes a permanent lock hold (deadlock).
  • Even an Error (like OutOfMemoryError) inside a synchronized block releases the lock.
  • Use javap -c to inspect the bytecode and verify both monitorexit instructions.

Definition

Yes, the JVM automatically releases the monitor lock when an exception is thrown inside a synchronized block because the compiler generates two monitorexit instructions: one for normal exit and one for exceptional exit.
  • Automatic release – when an exception is thrown inside a synchronized block, the JVM releases the monitor lock before propagating the exception. This happens for all exceptions, errors, and runtime exceptions.
  • Bytecode mechanism – the Java compiler generates two monitorexit instructions: one at the end of the normal code path and one in an automatically generated exception handler.
  • synchronized advantage – because lock release is automatic, you cannot accidentally forget to release the lock. This is a significant safety advantage over ReentrantLock.
  • ReentrantLock risk – with ReentrantLock, you must call lock.unlock() in a finally block. If you forget and an exception is thrown, the lock is held permanently, causing all waiting threads to be deadlocked.

When a teller inside the vault (synchronized block) suddenly faints (exception), the vault door automatically unlocks. The vault has a built-in emergency mechanism (the second monitorexit) that opens the door regardless of what happens inside. With a manual padlock (ReentrantLock), if the teller faints while holding the key, nobody else can enter unless someone explicitly takes the key from the teller's hand (finally block calling unlock).

Understanding the Concepts

Does synchronized release the lock on exception?

Yes. The JVM guarantees that the monitor lock is released when the thread exits a synchronized block, whether normally or via an exception.

JAVA
package com.bank.service;

import java.math.BigDecimal;

public class ExceptionReleaseDemo {
    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                synchronized (lock) {
                    System.out.println("T1: acquired lock");
                    throw new RuntimeException("Settlement failed!");
                    // Lock is released here despite the exception
                }
            } catch (RuntimeException e) {
                System.out.println("T1: exception caught, lock released");
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("T2: acquired lock after T1's exception");
            }
        });

        t1.start();
        t1.join();
        t2.start();
        t2.join();
    }
}
TEXT
T1: acquired lock
T1: exception caught, lock released
T2: acquired lock after T1's exception

T2 successfully acquires the lock after T1's exception because the lock was automatically released.

How does the bytecode look?

You can verify this by running javap -c on the compiled class. The compiler generates two monitorexit instructions.

TEXT
public void syncMethod();
  Code:
    0: aload_1
    1: monitorenter          // acquire lock
    2: ... method body ...
    8: aload_1
    9: monitorexit           // normal exit: release lock
   10: goto 18
   13: astore_2              // exception handler starts here
   14: aload_1
   15: monitorexit           // exceptional exit: release lock
   16: aload_2
   17: athrow                // rethrow the exception
   18: return

The second monitorexit at instruction 15 is automatically generated by the compiler. It ensures the lock is released even when an exception is thrown.

How does this compare to ReentrantLock?

With ReentrantLock, you must manually release the lock in a finally block. Forgetting is a critical bug.

JAVA
package com.bank.service;

import java.util.concurrent.locks.ReentrantLock;

public class LockComparisonDemo {
    private final ReentrantLock lock = new ReentrantLock();

    // CORRECT: unlock in finally
    public void correctApproach() {
        lock.lock();
        try {
            processSettlement(); // might throw!
        } finally {
            lock.unlock(); // always executed, even on exception
        }
    }

    // WRONG: unlock never reached on exception
    public void wrongApproach() {
        lock.lock();
        processSettlement(); // throws exception!
        lock.unlock();       // never reached! Lock held forever!
    }

    private void processSettlement() {
        throw new RuntimeException("DB connection failed");
    }
}

In the wrong approach, when processSettlement() throws, lock.unlock() is never called. Every thread waiting for this lock is permanently deadlocked.

What are the edge cases?

The lock is released even for Error types like OutOfMemoryError and StackOverflowError. However, after such severe errors, the JVM state may be corrupted and continued operation is unreliable.

When should you prefer synchronized over ReentrantLock?

Prefer synchronized for simple mutual exclusion because it is safer (automatic lock release) and simpler (no try-finally boilerplate). Use ReentrantLock only when you need features that synchronized cannot provide: tryLock(), timeout, interruptibility, fairness, or multiple Condition objects.

What should a senior engineer know?

  • Lock release on Error: the lock is released even on OutOfMemoryError, but the application may be in an inconsistent state. In production, catch OutOfMemoryError at the top level and trigger a graceful shutdown.
  • Nested synchronized: if a thread holds multiple monitors and an exception is thrown, all monitors are released as the stack unwinds through each synchronized block.
  • ReentrantLock hold count: if a thread calls lock.lock() three times (reentrant), it must call lock.unlock() three times. An exception after the third lock but before all three unlocks leaves the lock in a partially held state unless finally handles it correctly.

Visual Overview

DIAGRAM
  synchronized block bytecode:

  monitorenter ──▶ method body ──▶ monitorexit (normal)
       │                              │
       │          exception           │
       └──────────▶ monitorexit ──────▶ athrow
                    (exceptional)

  ReentrantLock (must be manual):

  lock.lock() ──▶ try { body } ──▶ finally { lock.unlock() }
       │                              │
       │          exception           │
       └──────────▶ finally ──────────▶ lock.unlock()
                    (developer must write this!)

Key Takeaway

When an exception is thrown inside a synchronized block, the JVM automatically releases the monitor lock. The compiler generates two monitorexit instructions in the bytecode: one for the normal exit path and one for the exceptional exit path. This makes synchronized blocks inherently safe: you cannot accidentally forget to release the lock.

This is a significant advantage over ReentrantLock, where lock.unlock() must be explicitly placed in a finally block. Forgetting this causes a permanent lock hold: every other thread waiting for the lock is deadlocked until the JVM is restarted.

Remember one sentence: synchronized always releases the lock on exception because the JVM generates a second monitorexit on the exceptional path, while ReentrantLock requires a manual finally block.

---

Share
Use ← → arrow keys to navigate