Concurrency

Read-Write Lock

Allow many readers OR one writer to hold a lock concurrently — optimizing read-heavy workloads.

3 min readDesigns · LLD & Patterns

Intent

When reads vastly outnumber writes, a plain mutex is wasteful — readers don't conflict with each other. A read-write lock lets multiple readers proceed in parallel while ensuring writes are exclusive.

Use when:

  • The workload is read-heavy (caches, configuration, metadata).
  • Reads don't modify state.
  • Writes must see consistent state.

Semantics

AcquiredOther readers can acquire?Other writers can acquire?
Read lock✅ yes❌ no
Write lock❌ no❌ no
Nothing✅ yes✅ yes

In short: N readers OR 1 writer.


Java's ReentrantReadWriteLock

import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.Lock;

public class Cache<K, V> {
    private final Map<K, V> data = new HashMap<>();
    private final ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
    private final Lock readLock = rw.readLock();
    private final Lock writeLock = rw.writeLock();

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

    public void put(K key, V value) {
        writeLock.lock();
        try {
            data.put(key, value);
        } finally {
            writeLock.unlock();
        }
    }

    public void clear() {
        writeLock.lock();
        try {
            data.clear();
        } finally {
            writeLock.unlock();
        }
    }
}

Multiple threads can get() simultaneously. put() and clear() block all readers and other writers until they're done.


When R/W Locks Pay Off

When R/W Locks Pay OffFlowchart

R/W locks have higher overhead than plain mutex. They only win when reads dominate (>80%).


Fair vs Non-fair Mode

new ReentrantReadWriteLock(true);   // fair — FIFO ordering
new ReentrantReadWriteLock(false);  // unfair — better throughput, default
ModeBehavior
FairThreads queue in arrival order. Prevents starvation but slower.
Non-fairWhoever wins the race gets the lock. Faster but readers can starve writers.

In non-fair mode, an unbroken stream of readers can prevent writers from ever acquiring the lock. The Java implementation has heuristics to avoid this in most cases, but it's a real risk.


Lock Downgrading (read after write)

// LEGAL: write -> read
writeLock.lock();
try {
    update();
    readLock.lock();    // acquire read while still holding write
} finally {
    writeLock.unlock();    // releases write, you still hold read
}
try {
    return readData();
} finally {
    readLock.unlock();
}

Useful when you want to do a write-then-read while preventing another writer from sneaking in.


Lock Upgrading (read to write) — NOT Allowed

readLock.lock();
// ... want to write now
writeLock.lock();   // DEADLOCK: read lock blocks the write lock

Two readers both trying to upgrade deadlock — each holds a read, each waits for a write. Java's ReentrantReadWriteLock does not support upgrading. Workaround:

readLock.lock();
boolean needWrite;
try {
    needWrite = checkSomething();
} finally {
    readLock.unlock();
}

if (needWrite) {
    writeLock.lock();
    try {
        // re-check; another thread might have done it
        if (checkSomething()) update();
    } finally {
        writeLock.unlock();
    }
}

Pattern: read → unlock read → acquire write → re-validate → write.


StampedLock — Newer, Faster

Java 8 introduced StampedLock with three modes: read, write, and optimistic read:

StampedLock lock = new StampedLock();

// Optimistic read — no actual locking, just a version stamp
long stamp = lock.tryOptimisticRead();
double currentX = x, currentY = y;

if (!lock.validate(stamp)) {       // someone wrote in the meantime?
    stamp = lock.readLock();        // fall back to real read lock
    try {
        currentX = x;
        currentY = y;
    } finally {
        lock.unlockRead(stamp);
    }
}

Optimistic reads are lock-free when there are no concurrent writes. Massive speedup for read-mostly hot paths.

ReentrantReadWriteLockStampedLock
Optimistic readNoYes
ReentrancyYesNo
ConditionsYesNo
PerformanceGoodBetter

StampedLock is faster but trickier. Use ReentrantReadWriteLock unless profiling says otherwise.


Real-world Examples

Use caseWhy R/W lock
In-memory cacheReads dominant; rare invalidation
Configuration registryReads frequent; updates infrequent
Routing tableLookups frequent; topology changes rare
Game world stateMany readers (rendering); one writer (sim tick)
Symbol table in compilerMany lookups; few additions

When to Not Use R/W Lock

  • Reads are short and fast. The R/W lock overhead may exceed the work.
  • Writes are frequent. R/W lock degenerates to a slower mutex.
  • You can use immutable snapshots. volatile reference + atomic swap is often faster:
private volatile Map<K,V> map = Map.of();

V get(K key) { return map.get(key); }    // lock-free read

synchronized void put(K key, V value) {
    Map<K,V> updated = new HashMap<>(map);
    updated.put(key, value);
    this.map = Map.copyOf(updated);       // single atomic publish
}

This is copy-on-write. Reads are completely lock-free. Writes pay the cost of copying.


Trade-offs

✅ Pros:

  • Parallel reads
  • Better throughput for read-heavy workloads

❌ Cons:

  • Higher overhead than plain mutex
  • Writer starvation in non-fair mode
  • No upgrade path; downgrade only
  • More complex than alternatives

Interview Tips

  • Mention R/W lock when the interviewer says "read-heavy" or "95% reads, 5% writes".
  • Discuss starvation and fair mode — interviewers test for awareness.
  • Mention StampedLock and copy-on-write as faster alternatives when applicable.

Finished reading?

Mark it done to track your progress in Designs.