Q35 of 40 · Core Java
Explain the synchronized keyword vs ReentrantLock — when would you choose each?
Core JavaSeniorsynchronizedreentrantlockconcurrencylocksthreadingjava
Short answer
Short answer: synchronized is the built-in JVM monitor mechanism: simple, guaranteed to be released on exception, but limited to block-scoped locking. ReentrantLock from java.util.concurrent.locks offers the same mutual exclusion but adds tryLock (non-blocking acquire), lockInterruptibly, fairness policies, and separate Condition objects for fine-grained wait/notify. Prefer synchronized unless you need one of those advanced features.
Detail
synchronized
// Method-level — locks on 'this'
public synchronized void addUser(String name) {
users.add(name);
}
// Block-level — explicit lock object, finer granularity
private final Object lock = new Object();
public void transfer(Account from, Account to, int amount) {
synchronized (lock) {
from.debit(amount);
to.credit(amount);
} // lock always released, even on exception
}
ReentrantLock
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;
private final ReentrantLock lock = new ReentrantLock(true); // fair
// tryLock — don't wait if lock unavailable
public boolean tryTransfer(Account from, Account to, int amount)
throws InterruptedException {
if (!lock.tryLock(100, TimeUnit.MILLISECONDS)) {
return false; // could not acquire, report failure
}
try {
from.debit(amount);
to.credit(amount);
return true;
} finally {
lock.unlock(); // MUST be in finally — ReentrantLock doesn't auto-release
}
}
// lockInterruptibly — abort if thread is interrupted while waiting
public void workUntilInterrupted() throws InterruptedException {
lock.lockInterruptibly();
try {
// ... work
} finally {
lock.unlock();
}
}
Feature comparison
| Feature | synchronized | ReentrantLock |
|---|---|---|
| Auto-release on exception | Yes | No — must use finally |
| tryLock (non-blocking) | No | Yes |
| Timed lock attempt | No | Yes |
| Interruptible lock wait | No | Yes |
| Fairness option | No | Yes (constructor arg) |
| Multiple Condition objects | No (one per monitor) | Yes |
| Reentrancy | Yes | Yes |
When to use ReentrantLock
- Need tryLock to avoid deadlock or implement timeout-based acquisition
- Need lockInterruptibly for responsive shutdown
- Need multiple Condition variables (producer/consumer with separate queues)
- Need fairness to prevent starvation
Otherwise, synchronized is simpler, less error-prone (no missed unlock), and the JVM optimises it well.
// WHAT INTERVIEWERS LOOK FOR
That synchronized auto-releases on exception but ReentrantLock requires a finally block. The tryLock use case. When fairness matters. Senior candidates know that the JVM can apply lock elision and coarsening to synchronized but not to ReentrantLock.
// COMMON PITFALL
Forgetting unlock() in the non-exceptional path when using ReentrantLock. If the lock is acquired and the try block completes normally but unlock() is only in a catch block, the lock leaks on normal exit. Always put unlock() in finally.