Skip to content

Locks — ReentrantLock, ReadWriteLock, StampedLock, and Condition

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Locks. We cover key concepts, practical examples, and best practices to help you master this topic.

Java's java.util.concurrent.locks package provides advanced locking mechanisms beyond synchronized, including ReentrantLock, ReadWriteLock, StampedLock, and Condition for flexible thread coordination. While synchronized is simple and sufficient for most cases, explicit locks offer try-lock, timed lock, interruptible lock, and multiple condition queues.

What You'll Learn

  • ReentrantLock: explicit lock with features
  • ReadWriteLock: multiple readers, single writer
  • StampedLock: optimistic read locking
  • Condition: signal/wait alternative to Object methods

Why It Matters

ReadWriteLock dramatically improves throughput for read-heavy workloads where readers never conflict with readers. StampedLock supports optimistic reads that avoid locking entirely when there is no contention.

Real-World Use

Caches use ReadWriteLock — many threads read, few write. Transaction logs use ReentrantLock with fairness to prevent starvation. StampedLock is used in high-throughput data structures.


ReentrantLock

import java.util.concurrent.locks.*;

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

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
}

Always release the lock in finally to prevent lock leaks.

tryLock

if (lock.tryLock()) {
    try {
        // critical section
    } finally {
        lock.unlock();
    }
} else {
    // alternative action
}

// With timeout
if (lock.tryLock(100, TimeUnit.MILLISECONDS)) { ... }

Fairness

// Fair lock — longest-waiting thread acquires first (slower)
ReentrantLock fairLock = new ReentrantLock(true);

Interruptible Lock

lock.lockInterruptibly();  // throws InterruptedException if thread is interrupted

ReadWriteLock

Allows multiple readers OR a single writer — never both:

class ReadWriteMap<K, V> {
    private final Map<K, V> map = new HashMap<>();
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();

    public V get(K key) {
        rwLock.readLock().lock();
        try {
            return map.get(key);
        } finally {
            rwLock.readLock().unlock();
        }
    }

    public void put(K key, V value) {
        rwLock.writeLock().lock();
        try {
            map.put(key, value);
        } finally {
            rwLock.writeLock().unlock();
        }
    }
}
  • Multiple threads can read concurrently — high throughput
  • Writing is exclusive — blocks all readers and other writers
  • Ideal for caches, configuration stores, lookup tables

StampedLock

StampedLock provides three modes: read, write, and optimistic read:

class Point {
    private double x, y;
    private final StampedLock lock = new StampedLock();

    public void move(double dx, double dy) {
        long stamp = lock.writeLock();
        try {
            x += dx;
            y += dy;
        } finally {
            lock.unlockWrite(stamp);
        }
    }

    public double distanceFromOrigin() {
        long stamp = lock.tryOptimisticRead();
        double currentX = x;
        double currentY = y;
        if (!lock.validate(stamp)) {
            stamp = lock.readLock();
            try {
                currentX = x;
                currentY = y;
            } finally {
                lock.unlockRead(stamp);
            }
        }
        return Math.sqrt(currentX * currentX + currentY * currentY);
    }
}

Optimistic read does not block — it reads optimistically and validates afterward. If a write occurred during the read, it falls back to a full read lock. This can be much faster in low-contention scenarios.

Condition

Condition provides await/signal instead of wait/notify, with the advantage of multiple condition queues per lock:

class BoundedBuffer<T> {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();
    private final List<T> buffer = new LinkedList<>();
    private final int capacity;

    public BoundedBuffer(int capacity) {
        this.capacity = capacity;
    }

    public void put(T item) throws InterruptedException {
        lock.lock();
        try {
            while (buffer.size() == capacity) {
                notFull.await();  // wait until not full
            }
            buffer.add(item);
            notEmpty.signal();   // signal consumers
        } finally {
            lock.unlock();
        }
    }

    public T take() throws InterruptedException {
        lock.lock();
        try {
            while (buffer.isEmpty()) {
                notEmpty.await();  // wait until not empty
            }
            T item = buffer.remove(0);
            notFull.signal();     // signal producers
            return item;
        } finally {
            lock.unlock();
        }
    }
}

Lock vs Synchronized

Feature synchronized Lock
Simplicity Simple, implicit More verbose
Try-lock No Yes
Timed acquisition No Yes
Interruptible No Yes
Fairness No (unfair) Configurable
Multiple conditions Single wait set Multiple Conditions
Performance (modern JVM) Excellent Comparable

Common Mistakes

  1. Forgetting to unlock in finally. If an exception occurs between lock and unlock, the lock is never released — causing Deadlock.
  2. Using ReentrantLock when synchronized suffices. If you do not need try-lock, timed lock, or fairness, synchronized is simpler.
  3. Downgrading from write lock to read lock. You can release the write lock and acquire the read lock, but ReentrantReadWriteLock does not support direct downgrade.
  4. Calling await() without checking the condition. Always use while (condition) not if (condition) — spurious wakeups can occur.
  5. Using StampedLock optimism incorrectly. Validate the stamp — if validation fails, the read may be inconsistent.

Practice Questions

1. What is the advantage of ReadWriteLock over ReentrantLock?
It allows multiple concurrent readers when no writer holds the lock, significantly improving throughput for read-heavy workloads.

2. What is an optimistic read in StampedLock?
A read that does not block — it reads the data and then validates that no write occurred concurrently. If a write occurred, it retries with a full read lock.

3. What is the difference between signal() and signalAll()?
signal() wakes up one waiting thread. signalAll() wakes up all waiting threads. Use signalAll() when threads may be waiting for different conditions.

4. How does a Condition differ from Object.wait/notify?
Condition allows multiple wait queues per lock, while Object's methods use a single wait set. Condition also supports fairness and interruptible waits.

5. What is a try-lock?
lock.tryLock() attempts to acquire the lock without blocking — returns true if acquired, false otherwise. Useful for avoiding deadlocks by backing off.

Challenge Question:
Implement a thread-safe ConcurrentCache<K, V> using ReadWriteLock. The cache supports get, put, invalidate, and clear. Use ConcurrentHashMap internally but protect bulk operations with the write lock. Also implement a getOrCompute(K key, Function<K, V> loader) that loads the value on cache miss.

FAQ

What does 'reentrant' mean in ReentrantLock?

A lock is reentrant if the same thread can acquire the lock multiple times without deadlocking. Each acquisition must be matched by an unlock. synchronized is also reentrant.

Can I upgrade a read lock to a write lock?

Not directly. ReentrantReadWriteLock does not support upgrade (it would deadlock if multiple readers try to upgrade). You must release the read lock before acquiring the write lock.

What is stamp validation in StampedLock?

lock.validate(stamp) returns true if no write has occurred since the stamp was acquired. If false, the optimistic read may be inconsistent and you must fall back to a full read lock.

Is Lock fair by default?

No. ReentrantLock is unfair by default (threads can 'barge' ahead of waiting threads). Fair locks preserve FIFO order but have lower throughput.

What is a spurious wakeup?

A condition wait that returns without being signaled. This can happen on some operating systems. Always use while (condition) instead of if (condition) when waiting.

Mini Project

Write a program LockDemo.java that:

  1. Implements a thread-safe inventory counter using ReentrantLock
  2. Implements a configuration cache using ReadWriteLock — many readers, occasional writes
  3. Implements a 3D point class using StampedLock with optimistic reads
  4. Implements a bounded buffer using Condition (await/signal)
  5. Measures throughput: synchronized vs ReentrantLock vs ReadWriteLock vs StampedLock
  6. Demonstrates tryLock to avoid deadlock with a lock-ordering example
  7. Shows the difference between fair and unfair locks with timing

What's Next

Creating threads manually is tedious and error-prone. Lesson 54 covers executors — thread pools, ScheduledExecutorService, invokeAll, invokeAny, and the ExecutorService framework for managing thread lifecycles.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro