Definition
- Read lock (shared) – multiple threads can hold the read lock simultaneously. Each readLock() call increments a shared reader count via CAS.
- Write lock (exclusive) – only one thread can hold the write lock, and it blocks all readers and other writers.
- Read-heavy benefit – when 95%+ operations are reads, 100 readers run in parallel instead of serializing through one lock. Throughput increases dramatically.
- Write-heavy cost – frequent write locks block all readers, and the CAS overhead on the shared reader counter adds contention between readers. In a 50:50 workload, a simple ReentrantLock is often faster.
- Downgrade – a thread holding the write lock can acquire the read lock and then release the write lock, downgrading to a read lock. This allows the thread to continue reading while letting other readers in.
- No upgrade – acquiring the write lock while holding the read lock causes deadlock because the thread blocks waiting for readers to release, but it is itself a reader.
ReentrantLock is like one door to National Bank of India's vault: only one person at a time, whether they are reading or writing. ReadWriteLock is like a wide door with a sign: multiple people can enter to read the register simultaneously, but when someone needs to write, everyone else must leave until the writing is done. If 95% of visitors are readers, the wide door handles 100 people at once instead of one at a time. But if 50% are writers, the constant clearing and re-filling of the room creates more overhead than a simple one-person door.
Understanding the Concepts
What is ReadWriteLock?
ReentrantReadWriteLock separates read operations from write operations to allow higher concurrency for read-heavy workloads. Multiple threads can hold the read lock simultaneously, but the write lock is exclusive: it blocks all readers and other writers.
package com.bank.concurrency;
import java.math.BigDecimal;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class BalanceCacheRWL {
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private BigDecimal balance = new BigDecimal("100000.00");
public BigDecimal getBalance() {
rwl.readLock().lock();
try {
return balance; // 100 threads can be here simultaneously
} finally {
rwl.readLock().unlock();
}
}
public void updateBalance(BigDecimal newBalance) {
rwl.writeLock().lock();
try {
balance = newBalance; // only ONE thread, all readers blocked
} finally {
rwl.writeLock().unlock();
}
}
}
When does ReadWriteLock help?
It helps when reads vastly outnumber writes. With a simple ReentrantLock, 100 read operations serialize: one at a time. With ReadWriteLock, all 100 run in parallel because the read lock is shared.
When does ReadWriteLock hurt?
When writes are frequent (50:50), the write lock constantly blocks all readers, and the CAS overhead on the shared reader count adds contention even between readers. A simple ReentrantLock has lower per-operation overhead and performs better.
How does write-to-read downgrade work?
package com.bank.concurrency;
import java.math.BigDecimal;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class DowngradeDemo {
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private BigDecimal balance = BigDecimal.ZERO;
public BigDecimal updateAndRead(BigDecimal amount) {
rwl.writeLock().lock();
try {
balance = balance.add(amount);
rwl.readLock().lock(); // acquire read WHILE holding write
} finally {
rwl.writeLock().unlock(); // release write, now hold only read
}
try {
return balance; // still holding read lock, other readers can join
} finally {
rwl.readLock().unlock();
}
}
}
Why is read-to-write upgrade not allowed?
// WRONG: read-to-write upgrade causes DEADLOCK
rwl.readLock().lock();
rwl.writeLock().lock(); // DEADLOCK!
// Write lock waits for ALL readers to release.
// But THIS thread is a reader and will not release until it gets the write lock.
// Circular dependency: deadlock.
The correct approach is to release the read lock first, acquire the write lock, then re-check the condition.
How do you choose between lock types?
| Workload | Recommended lock | Reason |
|---|---|---|
| 99:1 read:write | StampedLock optimistic | Zero CAS, zero contention |
| 95:5 read:write | ReentrantReadWriteLock | Parallel reads, occasional write blocking |
| 50:50 | ReentrantLock | Simplest, lowest overhead |
| Low contention | synchronized | JVM optimizes heavily |
What about fairness?
Fair mode (new ReentrantReadWriteLock(true)) uses FIFO ordering: the longest-waiting thread gets the lock next. This prevents writer starvation (where a continuous stream of readers prevents writers from ever acquiring the lock). Non-fair mode (default) allows barging, where a thread may acquire the lock ahead of waiting threads. Barging improves throughput but can starve individual threads.
What are common interview follow-up questions?
- Can you hold both read and write locks at the same time? Yes, during downgrade: hold write lock, acquire read lock, release write lock. But NOT during upgrade.
- Is ReadWriteLock better than ConcurrentHashMap? ConcurrentHashMap uses per-bucket locking which is usually sufficient. ReadWriteLock is for custom data structures or cross-structure operations.
Visual Overview
ReadWriteLock: Shared Readers, Exclusive Writer ═══════════════════════════════════════════════ Time → ┌──────────────────────────────────────┐ │ readLock: R1 R2 R3 R4 R5 │ 5 readers in parallel └──────────────────────────────────────┘ ┌──────┐ │ W1 │ writeLock: exclusive, readers blocked └──────┘ ┌──────────────────────────────────────┐ │ readLock: R6 R7 R8 R9 R10 │ readers resume └──────────────────────────────────────┘ Downgrade (allowed): writeLock() → readLock() → writeUnlock() → (now read only) Upgrade (DEADLOCK): readLock() → writeLock() → DEADLOCK! (thread waits for itself)
Key Takeaway
ReentrantReadWriteLock provides a shared read lock and an exclusive write lock, dramatically improving throughput for read-heavy workloads where 95% or more operations are reads. Multiple readers run in parallel instead of serializing through a single lock. Under write-heavy workloads, the CAS overhead on the shared reader count and the constant reader-blocking by writers make it slower than a simple ReentrantLock.
The lock supports write-to-read downgrade (acquire read lock while holding write lock, then release write lock) but does NOT support read-to-write upgrade, which causes deadlock. Fair mode prevents writer starvation but reduces throughput.
At National Bank of India, a balance cache serving 10,000 reads per second and 50 writes per second achieved P99 latency of 0.2 milliseconds with ReadWriteLock, compared to 5 milliseconds with ReentrantLock. For even higher read-to-write ratios, StampedLock's optimistic read eliminates the CAS contention entirely.
Remember one sentence: ReadWriteLock lets multiple readers proceed in parallel while writers get exclusive access, making it ideal for read-heavy data but costly for write-heavy workloads.
---