synchronized vs ReentrantLock
Both synchronized and ReentrantLock provide mutual exclusion and reentrancy in Java. synchronized is built-in and concise; ReentrantLock is explicit and offers more features (tryLock, fair lock, multiple conditions, lock status). This article compares them and gives when to use which, with examples and a short table.
Overview
- synchronized: Use the
synchronizedkeyword on a method or block; the lock is the object monitor. Reentrant: same thread can enter again. No explicit unlock (block exit or return does it). No tryLock, no fairness option, one condition per monitor. - ReentrantLock: Create a
ReentrantLockand calllock()/unlock(). Must unlock in finally to avoid leaking the lock. Supports tryLock (with or without timeout), fair lock, and multiple Condition instances. Built on AQS. - When to use which: Prefer synchronized for simple cases (one lock, no need for tryLock or fairness). Use ReentrantLock when you need tryLock, timeout, fairness, or multiple conditions, or when you want to hold the lock across a non-blocking structure (e.g. hand-over-hand locking).
Example
Example 1: synchronized — simple and concise
Javapublic synchronized void increment() { count++; } // or public void increment() { synchronized (lock) { count++; } }
- No explicit unlock; reentrant by default. Good when you do not need tryLock or fairness.
Example 2: ReentrantLock with tryLock
JavaReentrantLock lock = new ReentrantLock(); if (lock.tryLock(1, TimeUnit.SECONDS)) { try { doWork(); } finally { lock.unlock(); } } else { // alternative path: don't block }
- tryLock avoids blocking forever; useful for timeouts and “do something else if lock is busy.” Must unlock in finally.
Example 3: ReentrantLock with Condition
JavaReentrantLock lock = new ReentrantLock(); Condition notFull = lock.newCondition(); Condition notEmpty = lock.newCondition(); // One lock, two conditions (e.g. bounded queue: wait on notFull when full, wait on notEmpty when empty) lock.lock(); try { while (count == capacity) notFull.await(); // ... notEmpty.signal(); } finally { lock.unlock(); }
- synchronized has one wait set (wait/notify); ReentrantLock can have multiple Conditions so you can signal “not full” vs “not empty” separately. Useful for producer-consumer.
Example 4: Comparison table
| Feature | synchronized | ReentrantLock |
|---|---|---|
| Syntax | Keyword, compact | Explicit lock/unlock in finally |
| tryLock / timeout | No | Yes (tryLock, tryLock(time)) |
| Fair lock | No | Yes (constructor param) |
| Multiple conditions | No (one wait set) | Yes (newCondition()) |
| Lock status (e.g. isHeldByCurrentThread) | No | Yes |
| Default | Built-in, good for simple cases | When you need extra control |
Core Mechanism / Behavior
- synchronized: JVM uses monitorenter/monitorexit (or equivalent); the object header holds the lock state. Reentrancy is tracked in the monitor. No way to “try” or “wait with timeout” at the language level.
- ReentrantLock: Implemented with AQS; state = 0 (free) or 1+ (held, reentrant count). tryLock uses tryAcquire; fair lock uses queue order. Condition uses a separate wait queue per condition.
- Performance: In modern JVMs, synchronized is heavily optimized (biased locking, etc.); for typical use they are comparable. ReentrantLock can be faster under high contention when you use tryLock and avoid blocking.
Key Rules
- Prefer synchronized when you only need mutual exclusion and reentrancy; it is simpler and harder to misuse (no forgotten unlock). Use ReentrantLock when you need tryLock, timeout, fairness, or multiple conditions.
- Always call unlock() in a finally block when using ReentrantLock so that an exception does not leave the lock held. Prefer lock() immediately before try { ... } finally { unlock(); }.
- Use the same lock type consistently in a given component; mixing synchronized and ReentrantLock on the “same” logical lock is error-prone (they are different locks).
Decision Guide (What to Choose in Real Systems)
If your search query is simply "synchronized vs reentrantlock", this is the practical answer:
- Need only mutual exclusion and simple critical section -> synchronized.
- Need timeout / non-blocking fallback (
tryLock) -> ReentrantLock. - Need interruptible lock acquisition (
lockInterruptibly) -> ReentrantLock. - Need multiple condition queues (
notFull,notEmpty) -> ReentrantLock. - Need fairness option for starvation-sensitive workload -> ReentrantLock(true) (but expect lower throughput).
In other words: default to synchronized, switch to ReentrantLock only when feature requirements force it.
Deep Behavior Differences You Should Know
1) Interruptibility while waiting for lock
synchronized: waiting to enter monitor is not interruptible in the same way as lock APIs.ReentrantLock.lockInterruptibly(): can abort wait when thread is interrupted.
This matters in service shutdown, request cancellation, and timeout propagation.
2) Timed lock acquire
Javaif (lock.tryLock(200, TimeUnit.MILLISECONDS)) { try { /* critical */ } finally { lock.unlock(); } } else { // degrade / retry / return quickly }
For latency-sensitive APIs, timed lock helps avoid tail-latency spikes.
3) Fairness trade-off
- Fair lock can reduce starvation.
- But fair mode usually lowers throughput (more context switching, less barging optimization).
Use fair mode only when "who gets lock next" is a business requirement.
4) Condition precision
With monitor (wait/notify), all waiting threads share one wait set for that monitor.
With ReentrantLock, each Condition has its own queue, so you can signal targeted waiters.
This reduces useless wakeups in producer-consumer style queues.
Common Pitfalls
- Forgetting unlock() in exception paths (ReentrantLock only).
- Locking too large scope causing hidden contention.
- Mixing lock objects accidentally protecting same data with different locks.
- Using fairness by default and losing throughput for no reason.
- Calling blocking IO while holding lock, making contention far worse.
FAQ (Based on Search Queries)
synchronized vs reentrantlock which is better
Neither is universally better:
- Better simplicity/safety -> synchronized
- Better control/advanced features -> ReentrantLock
Choose by requirements, not by folklore benchmark.
synchronized vs reentrantlock performance
- Modern JVM optimized synchronized heavily.
- Under moderate contention, performance may be close.
- ReentrantLock can win when
tryLock/timeout avoids long blocking chains. - End-to-end latency depends more on lock scope and contention pattern than lock keyword choice.
when to use reentrantlock instead of synchronized
Use ReentrantLock when you need:
tryLockor timed acquire- interruptible acquire
- fair lock mode
- multiple condition queues
What's Next
See AQS Explained for how ReentrantLock is built. See Deadlock Detect and Prevent for lock ordering and tryLock. See Volatile and JMM for visibility when sharing state.