# Differences Between synchronized and ReentrantLock in Java: A Complete Comparison

> Compare synchronized vs ReentrantLock in Java. Learn about their differences in locking, management, and features like fairness and interruptibility to choose the right tool for your concurrency needs.

- Repository: [Guide/JavaGuide](https://github.com/Snailclimb/JavaGuide)
- Tags: deep-dive
- Published: 2026-02-24

---

**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`](https://github.com/Snailclimb/JavaGuide/blob/main/docs/java/concurrent/java-concurrent-questions-02.md) and [`docs/java/concurrent/reentrantlock.md`](https://github.com/Snailclimb/JavaGuide/blob/main/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`](https://github.com/Snailclimb/JavaGuide/blob/main/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`](https://github.com/Snailclimb/JavaGuide/blob/main/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`](https://github.com/Snailclimb/JavaGuide/blob/main/docs/java/concurrent/java-concurrent-questions-02.md).

**`ReentrantLock`** supports both fair and non-fair modes via its constructor:

```java
// 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:

```java
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)`:

```java
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:

```java
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`](https://github.com/Snailclimb/JavaGuide/blob/main/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:

```java
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:

```java
lock.lock();
try {
    // Critical section
} finally {
    lock.unlock();  // Must manually release
}

```

The explicit pattern in [`docs/java/concurrent/java-concurrent-questions-02.md`](https://github.com/Snailclimb/JavaGuide/blob/main/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`](https://github.com/Snailclimb/JavaGuide/blob/main/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**: `synchronized` uses JVM monitors (`monitorenter/monitorexit`); `ReentrantLock` uses JDK-level AQS.
- **Fairness**: `synchronized` is always non-fair; `ReentrantLock` supports fair mode via constructor.
- **Interruption**: `synchronized` blocks cannot be interrupted; `ReentrantLock` offers `lockInterruptibly()`.
- **Timeouts**: Only `ReentrantLock` provides `tryLock(timeout, unit)`.
- **Conditions**: `synchronized` has one implicit condition; `ReentrantLock` supports multiple explicit `Condition` objects.
- **Syntax**: `synchronized` is automatic; `ReentrantLock` requires manual `try … finally` management.

## 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`](https://github.com/Snailclimb/JavaGuide/blob/main/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`](https://github.com/Snailclimb/JavaGuide/blob/main/docs/java/concurrent/java-concurrent-questions-02.md).