Differences Between synchronized and ReentrantLock in Java: A Complete Comparison
Both synchronized and ReentrantLock provide re-entrant locking, but synchronized is a JVM-level keyword with automatic lock management, while ReentrantLock is a JDK class offering configurable fairness, interruptible acquisition, timed try-locks, and multiple condition queues.
The Snailclimb/JavaGuide repository documents these concurrency mechanisms extensively across docs/java/concurrent/java-concurrent-questions-02.md and docs/java/concurrent/reentrantlock.md. While both constructs protect critical sections and ensure memory visibility, they differ fundamentally in implementation layer, flexibility, and control granularity.
Implementation Layer: JVM Bytecode vs JDK API
The most fundamental difference lies in where the locking logic resides.
synchronized is a Java language keyword whose implementation lives inside the JVM. The compiler translates synchronized blocks into monitorenter and monitorexit bytecode instructions, and the JVM handles the underlying monitor operations directly. As documented in docs/java/concurrent/java-concurrent-questions-02.md, this makes synchronized behavior dependent on the specific JVM implementation.
ReentrantLock is a pure Java class (java.util.concurrent.locks.ReentrantLock) that implements the Lock interface. Its behavior is defined in the JDK source code and built on top of the AbstractQueuedSynchronizer (AQS) framework. According to the guide in docs/java/concurrent/reentrantlock.md, this API-level implementation allows ReentrantLock to expose advanced features that JVM monitors cannot easily support.
Lock Fairness Configuration
synchronized is always non-fair. When a thread releases a monitor, the JVM makes no guarantees about which waiting thread acquires it next. The longest-waiting thread may be passed over by threads that arrived later, as noted in the comparison table within docs/java/concurrent/java-concurrent-questions-02.md.
ReentrantLock supports both fair and non-fair modes via its constructor:
// Fair lock - threads acquire in arrival order
ReentrantLock fairLock = new ReentrantLock(true);
// Non-fair lock (default) - higher throughput but potential starvation
ReentrantLock nonFairLock = new ReentrantLock();
Fair mode reduces starvation risk but may decrease overall throughput.
Interruptible Lock Acquisition
A thread blocked on a synchronized lock cannot be interrupted. The thread will remain in the BLOCKED state until the monitor becomes available, ignoring Thread.interrupt() calls.
ReentrantLock provides lockInterruptibly(), which allows a waiting thread to respond to interruption:
public void interruptibleTask() {
try {
lock.lockInterruptibly(); // Can throw InterruptedException
try {
// Critical section
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
// Handle cancellation properly
Thread.currentThread().interrupt();
}
}
This capability is essential for implementing responsive cancellation policies in long-running operations.
Timeout Support
synchronized offers no built-in timeout mechanism. A thread either acquires the lock immediately or waits indefinitely until the monitor is released.
ReentrantLock supports timed acquisition via tryLock(long timeout, TimeUnit unit):
if (lock.tryLock(2, TimeUnit.SECONDS)) {
try {
// Critical section
} finally {
lock.unlock();
}
} else {
// Handle failure to acquire within timeout
}
This prevents indefinite blocking and enables deadlock recovery strategies.
Condition Variables
synchronized associates exactly one condition queue (monitor) with each object. Threads must use wait(), notify(), and notifyAll() for coordination, which limits fine-grained signaling.
ReentrantLock allows creation of multiple Condition objects per lock via newCondition(). This enables selective wake-ups for different waiting threads:
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
public void put(int item) throws InterruptedException {
lock.lock();
try {
while (count == buffer.length) {
notFull.await(); // Wait for space
}
buffer[putIndex] = item;
notEmpty.signal(); // Wake only consumers
} finally {
lock.unlock();
}
}
As shown in docs/java/concurrent/java-concurrent-questions-02.md, this pattern is impossible with synchronized without creating additional objects.
Syntax and Error Handling
synchronized uses declarative syntax that ensures automatic lock release:
public synchronized void method() {
// Lock released automatically on exit
}
public void block() {
synchronized (this) {
// Lock released automatically even if exception thrown
}
}
ReentrantLock requires explicit lock() and unlock() calls wrapped in try … finally blocks:
lock.lock();
try {
// Critical section
} finally {
lock.unlock(); // Must manually release
}
The explicit pattern in docs/java/concurrent/java-concurrent-questions-02.md demonstrates that forgetting the finally block creates a risk of permanent lock retention.
Performance Characteristics
Historically, synchronized was considered heavyweight, but JDK 6+ introduced significant optimizations including biased locking, lightweight locking, and lock coarsening. According to docs/java/concurrent/java-concurrent-questions-02.md, these optimizations make synchronized highly efficient for low-contention scenarios.
ReentrantLock carries slightly higher overhead due to additional method calls and AQS state management. However, for high-contention scenarios requiring the advanced features above, the flexibility outweighs the marginal performance cost.
Summary
- Implementation:
synchronizeduses JVM monitors (monitorenter/monitorexit);ReentrantLockuses JDK-level AQS. - Fairness:
synchronizedis always non-fair;ReentrantLocksupports fair mode via constructor. - Interruption:
synchronizedblocks cannot be interrupted;ReentrantLockofferslockInterruptibly(). - Timeouts: Only
ReentrantLockprovidestryLock(timeout, unit). - Conditions:
synchronizedhas one implicit condition;ReentrantLocksupports multiple explicitConditionobjects. - Syntax:
synchronizedis automatic;ReentrantLockrequires manualtry … finallymanagement.
Frequently Asked Questions
Can ReentrantLock be used as a direct replacement for synchronized?
While ReentrantLock can replace synchronized for mutual exclusion, it requires careful handling. You must wrap lock() calls in try … finally blocks to ensure unlock() is always called, whereas synchronized releases automatically. Additionally, ReentrantLock is an object that must be stored as a field, unlike the monitor associated with every Java object.
Which performs better: synchronized or ReentrantLock?
For low-contention scenarios, synchronized typically performs better due to JVM optimizations like biased locking documented in docs/java/concurrent/java-concurrent-questions-02.md. ReentrantLock incurs slightly higher overhead from additional indirection, but its advanced features (fairness, conditions) provide better throughput in complex contention patterns where synchronized would cause bottlenecks.
When should I choose synchronized over ReentrantLock?
Choose synchronized when you need simple mutual exclusion without advanced requirements. It is sufficient for most use cases, offers cleaner syntax, and is less error-prone due to automatic lock release. The JavaGuide repository recommends synchronized as the default choice unless you specifically need fairness, interruptibility, timeouts, or multiple condition variables.
Is ReentrantLock re-entrant like synchronized?
Yes, both mechanisms support re-entrant locking. A thread that already holds the lock can acquire it again without deadlocking. The hold count tracks recursive acquisitions, and the lock releases only when the count reaches zero. This behavior is confirmed in the re-entrancy discussion within docs/java/concurrent/java-concurrent-questions-02.md.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →