The choice between a standard ReentrantLock and a ReentrantReadWriteLock usually comes down to one metric: the Read-to-Write ratio.
If your threads are mostly looking at data without changing it, a standard lock creates a massive, unnecessary bottleneck. If your threads are constantly updating data, the overhead of a ReadWriteLock actually makes it slower than a simple lock.
1. Scenario A: High-Contention Updates (Use ReentrantLock)
Scenario: A Counter or a Bank Account where every single thread that enters is there to modify the value (e.g., increment() or deposit()).
import java.util.concurrent.locks.ReentrantLock;
public class AtomicCounter {
private int count = 0;
private final ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock(); // Only ONE thread can enter, period.
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
lock.lock(); // Even readers are blocked if someone is incrementing
try {
return count;
} finally {
lock.unlock();
}
}
}
Why use ReentrantLock here?
Since almost every operation is a “Write” (count++), there is no benefit to allowing multiple readers. A standard ReentrantLock is lightweight and has less internal management overhead than a ReadWriteLock.
2. Scenario B: High-Read Metadata (Use ReentrantReadWriteLock)
Scenario: A Product Catalog or a Configuration Cache. Thousands of users are checking the “Price” or “Settings” every second, but an admin only updates the price once or twice an hour.
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.HashMap;
import java.util.Map;
public class ProductCatalog {
private final Map<String, Double> prices = new HashMap<>();
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
public Double getPrice(String productId) {
rwLock.readLock().lock(); // MULTIPLE threads can hold this at once
try {
return prices.get(productId);
} finally {
rwLock.readLock().unlock();
}
}
public void updatePrice(String productId, Double newPrice) {
rwLock.writeLock().lock(); // EXCLUSIVE: Blocks all readers and other writers
try {
prices.put(productId, newPrice);
} finally {
rwLock.writeLock().unlock();
}
}
}
Why use ReentrantReadWriteLock here?
If you used a standard ReentrantLock, User A looking at the price of “Laptop” would block User B looking at the price of “Phone.” With a ReadLock, 1,000 users can read the catalog simultaneously. The only time anyone waits is when the admin triggers a WriteLock.
3. The Key Differences at a Glance
| Feature | ReentrantLock |
ReentrantReadWriteLock |
|---|---|---|
| Concurrency Style | Pessimistic / Mutual Exclusion | Shared-Read / Exclusive-Write |
| Readers vs. Readers | Blocking (One by one) | Non-Blocking (Simultaneous) |
| Readers vs. Writers | Blocking | Blocking |
| Performance Cost | Low overhead | Higher overhead (managing two lock states) |
| Best For… | Frequent updates, small critical sections. | Large data structures with 90%+ Read operations. |
Critical “Rule of Thumb”
- Use
ReentrantLockif the time spent inside the lock is very short (e.g., updating a primitive) or if writes happen frequently. - Use
ReentrantReadWriteLockif the “Read” operation is expensive (e.g., iterating through a large Map) and writes are rare.