1. The “Why”
Standard locks (synchronized, ReentrantLock) are Mutual Exclusion locks—they don’t care if a thread is reading or writing; they block everyone else.
ReentrantReadWriteLock recognizes that multiple threads reading the same data simultaneously is perfectly safe. It only enforces “Mutual Exclusion” when a thread needs to write (modify) the data.
2. Comparison: ReentrantLock vs. ReadWriteLock
| Feature | ReentrantLock |
ReentrantReadWriteLock |
|---|---|---|
| Read/Read | Blocking (One at a time). | Non-Blocking (Infinite simultaneous readers). |
| Read/Write | Blocking. | Blocking (Reader waits for Writer). |
| Write/Write | Blocking. | Blocking (Only one Writer at a time). |
| Best Use Case | General purpose, frequent updates. | Databases, Caches, “Read-Heavy” metadata. |
3. The “Golden” Snippet: A Simple Database
In this example, we create a SimpleDatabase where many threads can query prices at the same time, but an update thread can safely modify them without causing data races.
import java.util.TreeMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.Lock;
public class SimpleDatabase {
private TreeMap<Integer, Integer> inventory = new TreeMap<>();
private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
// The "Read" side of the lock
private Lock readLock = rwLock.readLock();
// The "Write" side of the lock
private Lock writeLock = rwLock.writeLock();
public int getPrice(int productId) {
readLock.lock();
try {
// Multiple threads can be here at the same time
return inventory.getOrDefault(productId, 0);
} finally {
readLock.unlock();
}
}
public void updatePrice(int productId, int newPrice) {
writeLock.lock();
try {
// While a thread is here, NO ONE can read or write
inventory.put(productId, newPrice);
} finally {
writeLock.unlock();
}
}
}
Code Explanation:
- The Lock Split: We initialize one
ReentrantReadWriteLockbut extract two differentLockobjects from it:readLock()andwriteLock(). - Shared Reads: When
readLock.lock()is called, the JVM checks: “Is anyone writing?” If no, the thread enters. Ten other threads can callreadLock.lock()and enter simultaneously. - Exclusive Writes: When
writeLock.lock()is called, the thread waits until all current readers have finished and unlocked. Once the writer is inside, any new readers are blocked until the writer is done.
Example Output:
Thread-Reader-1: Reading Price... (Success)
Thread-Reader-2: Reading Price... (Success - Parallel with Reader 1)
Thread-Writer: Requesting Write Lock... (Waiting for Readers to finish)
Thread-Reader-3: Requesting Read Lock... (Blocked by waiting Writer)
Thread-Writer: Write Lock Acquired. Updating...
Thread-Writer: Released.
Thread-Reader-3: Read Lock Acquired. Price is now updated.
4. The Gotchas
- Write Starvation: If you have a constant stream of readers, a writer might wait forever to get in. Java’s
ReentrantReadWriteLockhandles this by prioritizing the “next in line,” but in very heavy read systems, writers can still struggle. - No “Upgrade” directly: You cannot “upgrade” a lock from Read to Write while holding the Read lock (this causes a Deadlock). You must
unlock()the read lock and thenlock()the write lock. - Complexity Overhead: If your “Read” operation is extremely fast (like just reading a single
int), the overhead of managing aReadWriteLockmight actually be slower than a simplesynchronizedblock. Use this only when the “Read” operation takes some time or happens extremely frequently.