How do I use ReentrantLock vs synchronized?

In Java, both synchronized and ReentrantLock are used to manage thread safety, but they offer different levels of control and flexibility.

1. synchronized Keyword

The synchronized keyword is the built-in mechanism in Java for mutual exclusion. It is simpler to use because the JVM automatically handles acquiring and releasing the lock.

  • Usage: Can be applied to methods or used as a block.
  • Automatic Release: The lock is automatically released when the thread exits the block or method, even if an exception occurs.
  • Performance: In modern JVMs, synchronized is highly optimized (biased locking, lightweight locking) and often performs just as well as ReentrantLock.

Example:

package org.kodejava.util.concurrent;

public class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public void incrementBlock() {
        synchronized (this) {
            count++;
        }
    }
}

2. ReentrantLock Class

ReentrantLock (from java.util.concurrent.locks) is an explicit lock implementation that provides advanced features not available with synchronized.

  • Manual Control: You must explicitly call lock() and unlock(). It is critical to use a try-finally block to ensure the lock is released.
  • Fairness: You can create a “fair” lock that gives preference to the longest-waiting thread.
  • Try Lock: tryLock() allows a thread to attempt to acquire the lock without blocking indefinitely.
  • Interruptible: A thread waiting for a ReentrantLock can be interrupted via lockInterruptibly().

Example:

package org.kodejava.util.concurrent;

import java.util.concurrent.locks.ReentrantLock;

public class LockCounter {
    private final ReentrantLock lock = new ReentrantLock();
    private int count = 0;

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            // Always unlock in finally to prevent deadlocks
            lock.unlock();
        }
    }
}

Key Differences

Feature synchronized ReentrantLock
Ease of Use Very easy; handled by JVM. Requires manual try-finally.
Fairness Not supported. Supported (optional constructor parameter).
Flexibility Rigid block structure. Highly flexible (can lock in one method, unlock in another).
Non-blocking No (thread always waits). Yes, via tryLock().
Interruptibility No. Yes, via lockInterruptibly().

Which should you choose?

  • Use synchronized by default. It’s cleaner, less error-prone, and sufficient for most basic thread-safety needs.
  • Use ReentrantLock when you need advanced features like:
    • Timing out while waiting for a lock (tryLock(timeout)).
    • Allowing the lock attempt to be interrupted.
    • Using a “Fair” lock strategy.
    • Complex locking structures that aren’t strictly nested.

If you are debugging concurrency issues, IntelliJ IDEA provides tools to inspect thread states and detect blocked monitors [1]. You can also use the Threads tab in the Debugger to see which thread owns a monitor or is waiting for one [2].

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.