Detailed explanation
Both synchronized and ReentrantLock provide mutual exclusion and memory visibility when threads share mutable state. Both are reentrant: a thread that already owns a lock can acquire it again without deadlocking itself. Reentrancy does not prevent deadlocks involving multiple locks. Releasing either lock establishes a happens-before relationship with a subsequent successful acquisition of that same lock, making earlier writes visible to the acquiring thread. Interviewers expect these shared guarantees before the differences.
synchronized is a Java language construct backed by an object's intrinsic monitor. An instance synchronized method locks this; a static synchronized method locks the corresponding Class object; a synchronized block locks its specified object. The JVM releases the monitor automatically when execution leaves the synchronized region, including through an exception. Monitor entry offers neither a timeout nor interruptible acquisition. However, saying it 'always blocks indefinitely' is misleading: uncontended acquisition proceeds immediately, and a waiting thread can acquire the monitor after release; there is simply no caller-specified waiting limit.
ReentrantLock is an explicit lock from java.util.concurrent.locks, implemented using AbstractQueuedSynchronizer's acquisition state and waiting queue machinery. Its lock() method waits without an acquisition timeout, while tryLock() returns immediately with success or failure. Timed tryLock waits up to a specified duration, and lockInterruptibly() allows interruption while acquiring the lock. Timed acquisition is also interruptible, so the supplied example must handle or declare InterruptedException. After successful acquisition, put the protected work inside try and unlock in finally. Unlike synchronized, explicit locking does not automatically release ownership when your code throws an exception.
ReentrantLock also supports optional fairness and multiple Condition objects for separate waiting queues, such as 'not empty' and 'not full'. A fair lock favours longer-waiting threads under contention, but does not guarantee thread-scheduling fairness; untimed tryLock can bypass that policy. synchronized instead uses the monitor's single wait set through wait, notify, and notifyAll. Prefer synchronized for straightforward scoped locking, and ReentrantLock when its extra controls solve a concrete requirement. Neither is universally faster: performance depends on workload, contention, and JVM behaviour, while fairness can reduce throughput.
Key points to remember
- Both provide reentrant mutual exclusion and happens-before visibility guarantees.
- synchronized automatically releases its monitor; ReentrantLock requires explicit unlock in finally.
- ReentrantLock supports immediate, timed, and interruptible acquisition; synchronized monitor entry does not.
- ReentrantLock offers optional fairness, but untimed tryLock does not honour the fairness policy.
- ReentrantLock supports multiple Condition wait queues; each intrinsic monitor has one wait set.
Common mistakes candidates make
- Claiming synchronized always blocks indefinitely or is always slower than ReentrantLock.
- Calling unlock after a failed tryLock, or forgetting finally after successful acquisition.
- Confusing interruptible Object.wait with interruptible synchronized monitor acquisition.
Likely follow-up questions
What happens if a thread waiting for synchronized is interrupted?
Interruption does not cancel intrinsic monitor acquisition or make it throw InterruptedException. The thread continues waiting to acquire the monitor, with its interrupt status set.
How does Condition.await differ from Thread.sleep?
Condition.await atomically releases its associated lock while waiting and reacquires it before returning; Thread.sleep does not release locks. Check the condition predicate in a loop because spurious wakeups are possible and another thread may change the state before reacquisition.
When should you enable fairness on ReentrantLock?
Consider fairness when reducing starvation risk and favouring longer-waiting threads matters more than maximum throughput. It can reduce throughput, does not control thread scheduling, and does not prevent untimed tryLock from acquiring ahead of queued threads.
Interview tip: Start with the shared guarantees, contrast automatic release with acquisition controls, and use the timed tryLock example to explain successful acquisition, interruption, and finally-based cleanup.
