0Pricing
Java Academy · Lesson

ReentrantLock vs synchronized

Compare ReentrantLock features (tryLock, lockInterruptibly, fairness) with the synchronized keyword.

ReentrantLock vs synchronized is a free Java Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Java Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

The synchronized Keyword

Java's synchronized keyword provides mutual exclusion using the object's intrinsic lock (monitor). Simple but limited — no timeout, no fairness, no interruptibility.

class Counter {
    private int count = 0;
    
    synchronized void increment() {
        count++; // only one thread at a time
    }
    
    synchronized int get() { return count; }
}

Counter c = new Counter();
// Multiple threads can safely call increment()

ReentrantLock Basics

ReentrantLock is an explicit lock from java.util.concurrent.locks. It provides the same mutual exclusion as synchronized but with more control. The lock must be manually released in a finally block.

import java.util.concurrent.locks.*;

class SafeCounter {
    private int count = 0;
    private final ReentrantLock lock = new ReentrantLock();
    
    void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock(); // always release in finally!
        }
    }
    
    int get() {
        lock.lock();
        try { return count; }
        finally { lock.unlock(); }
    }
}

tryLock: Non-blocking Acquisition

tryLock() attempts to acquire the lock without blocking. Returns true if successful, false if the lock is held by another thread.

ReentrantLock lock = new ReentrantLock();

if (lock.tryLock()) {
    try {
        System.out.println("Got the lock, doing work");
    } finally {
        lock.unlock();
    }
} else {
    System.out.println("Lock busy, skipping or retrying");
}

tryLock with Timeout

Wait up to a specified time to acquire the lock:

try {
    if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
        try {
            // work with protected resource
            System.out.println("Acquired within 500ms");
        } finally {
            lock.unlock();
        }
    } else {
        System.out.println("Timed out waiting for lock");
    }
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}

lockInterruptibly

lockInterruptibly() acquires the lock unless the thread is interrupted — useful for cancellable tasks waiting for a lock:

try {
    lock.lockInterruptibly(); // throws InterruptedException if interrupted
    try {
        // do work
    } finally {
        lock.unlock();
    }
} catch (InterruptedException e) {
    System.out.println("Interrupted while waiting for lock");
    Thread.currentThread().interrupt();
}

Reentrancy

Both synchronized and ReentrantLock are reentrant — a thread that already holds the lock can acquire it again without deadlocking:

synchronized void outer() {
    System.out.println("outer");
    inner(); // same thread re-enters — OK
}

synchronized void inner() {
    System.out.println("inner"); // same lock, same thread
}

// ReentrantLock:
ReentrantLock lock = new ReentrantLock();
lock.lock();
lock.lock(); // acquire again — hold count = 2
lock.unlock(); // hold count = 1
lock.unlock(); // hold count = 0, lock released

Fairness Policy

Create a fair ReentrantLock to grant access in FIFO order — prevents thread starvation at the cost of lower throughput:

// Unfair (default): no ordering guarantee, better throughput
ReentrantLock unfair = new ReentrantLock();

// Fair: threads acquire in arrival order
ReentrantLock fair = new ReentrantLock(true);

System.out.println(fair.isFair()); // true

Condition Variables

ReentrantLock provides Condition objects — more flexible than wait()/notify():

ReentrantLock lock2 = new ReentrantLock();
Condition notEmpty = lock2.newCondition();
Queue<String> queue = new LinkedList<>();

// Producer
lock2.lock();
try {
    queue.offer("item");
    notEmpty.signal(); // wake one waiting thread
} finally { lock2.unlock(); }

// Consumer
lock2.lock();
try {
    while (queue.isEmpty()) notEmpty.await(); // wait & release lock
    System.out.println(queue.poll());
} finally { lock2.unlock(); }

When to Use ReentrantLock vs synchronized

Use synchronized when:

  • Simple mutual exclusion is needed
  • No timeout/fairness/interruptibility required

Use ReentrantLock when:

  • tryLock() or tryLock(timeout) needed
  • lockInterruptibly() needed
  • Fair ordering required
  • Multiple Condition variables needed

Lock Count and Monitoring

ReentrantLock provides diagnostic methods:

ReentrantLock lock3 = new ReentrantLock();
lock3.lock();
lock3.lock(); // reentrant — hold count 2

System.out.println(lock3.getHoldCount());    // 2
System.out.println(lock3.isHeldByCurrentThread()); // true
System.out.println(lock3.isLocked());        // true
System.out.println(lock3.getQueueLength());  // 0 (no waiting threads)

lock3.unlock();
lock3.unlock(); // fully released

Common Mistake: Unlock Without Lock

Calling unlock() when the current thread doesn't hold the lock throws IllegalMonitorStateException. Always pair lock/unlock or use try-finally:

ReentrantLock lock4 = new ReentrantLock();
try {
    // If lock() is never called (e.g., skipped by exception before this line)
    lock4.unlock(); // throws IllegalMonitorStateException
} catch (IllegalMonitorStateException e) {
    System.out.println("Must lock before unlock!");
}

Quick Check

Which ReentrantLock method allows a thread to acquire the lock or return immediately without blocking if the lock is unavailable?

Recap: ReentrantLock vs synchronized

Key takeaways:

  • Both provide mutual exclusion and reentrancy
  • ReentrantLock adds: tryLock, lockInterruptibly, fairness, Conditions
  • Always release in finally block
  • synchronized is simpler — prefer it when its features suffice
  • ReentrantLock for advanced scenarios: timeouts, interruptibility, multiple conditions

Frequently asked questions

Is the “ReentrantLock vs synchronized” lesson free?

Yes — the full text of “ReentrantLock vs synchronized” is free to read here on the web, and the Java Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Java Academy course, upgrade to CoddyKit PRO.

What will I learn in “ReentrantLock vs synchronized”?

Compare ReentrantLock features (tryLock, lockInterruptibly, fairness) with the synchronized keyword. You practise Java Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Java Academy?

No prior experience is required. Java Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “ReentrantLock vs synchronized” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Java Academy lesson?

Yes. Every Java Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. ReentrantLock vs synchronized
  2. ReadWriteLock for Reader-Writer Scenarios
  3. Atomic Variables: Lock-Free Updates
  4. StampedLock and Optimistic Reads
← Back to Java Academy