synchronized vs Lock vs ReentrantLock
Understand monitor lock vs lock abstraction vs concrete advanced lock implementation.
📌 Key Idea
synchronized is a language-level monitor lock. Lock is the interface abstraction. ReentrantLock is a concrete Lock implementation with advanced APIs. For simple critical sections, default to synchronized; use ReentrantLock only for concrete control needs.Visual
🧩 `synchronized` (monitor lock)
Best default for simple critical sections.
synchronized (mutex) {
// critical section
} // auto-release on exit/exception
Pros:
✅ concise and hard to misuse
✅ auto-unlock safety🛠 `ReentrantLock` (explicit API)
Use when you need lock-acquisition control.
if (lock.tryLock(100, MILLISECONDS)) {
try {
// critical section
} finally {
lock.unlock();
}
}
Extras:
✅ tryLock / timeout / interruptible wait
✅ fairness + Condition queues🧱 `Lock` (interface contract)
`ReentrantLock` is one implementation of `Lock`.
Lock is abstraction: lock(), unlock() tryLock(), lockInterruptibly() newCondition() You code to interface and plug concrete impl.
Decision rule: default to simplicity, escalate only for specific lock-control requirements.
When to Use
- Use synchronized for straightforward critical sections.
- Use ReentrantLock when you need tryLock/timeout or interruptible lock wait.
- Use ReentrantLock with multiple condition queues for producer-consumer variants.
When NOT to Use
- Do not use ReentrantLock just to look advanced; complexity rises.
- Avoid coarse synchronized blocks around slow I/O.
- Avoid any lock when immutable design can remove shared state.
Anti-Pattern (What NOT to Do)
Manual lock without
finallyBadjavaimport java.util.concurrent.locks.ReentrantLock;
class Wallet {
private final ReentrantLock lock = new ReentrantLock();
private int balance = 0;
void deposit(int amount) {
lock.lock();
balance += amount;
if (amount < 0) throw new IllegalArgumentException();
lock.unlock(); // never reached on exception
}
}If exception occurs before
unlock, other threads can block forever.The Right Way
Safe
ReentrantLock usageGoodjavaimport java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
class Wallet {
private final ReentrantLock lock = new ReentrantLock();
private int balance = 0;
boolean deposit(int amount) throws InterruptedException {
if (!lock.tryLock(100, TimeUnit.MILLISECONDS)) return false;
try {
if (amount < 0) throw new IllegalArgumentException();
balance += amount;
return true;
} finally {
lock.unlock();
}
}
}Always combine lock acquire with
try/finally.synchronized vs Lock vs ReentrantLock
| Aspect | `synchronized` | `Lock` (interface) | `ReentrantLock` (implementation) |
|---|---|---|---|
| What it is | Language keyword + object monitor | Concurrency API contract | Most-used concrete Lock implementation |
| Unlock behavior | Auto release on block exit | API requires explicit unlock by impl contract | Manual unlock() in finally |
| Timed / non-blocking acquire | ❌ Not built in | ✅ Declares tryLock APIs | ✅ tryLock() / timeout |
| Interruptible lock wait | ❌ Limited | ✅ Declares lockInterruptibly() | ✅ Supported |
| Condition queues | One implicit monitor queue (wait/notify) | Declares newCondition() | Multiple explicit Conditions |
| Best use | Simple critical sections | Code-to-interface design | Advanced locking behavior |
Common Confusions Cleared Up
This is not about “faster” — it is about control vs simplicity
In modern JVMs, both can perform well depending on workload. Interviewers prefer a correctness-first answer: choose the primitive that matches required behavior, then measure.
Interview Q&A
Quick Cheat Sheet
Default choice
synchronized for simple mutual exclusion.Use
ReentrantLock whenNeed timeout/interrupt/fairness/multiple conditions.Non-negotiable ruleIf you call
lock(), unlock in finally.Interview framingCorrectness + required features first, performance after measurement.