ReentrantReadWriteLock
ReentrantReadWriteLock is a more complex synchronizer that allows multiple threads to read a shared resource simultaneously, as long as no thread is writing to it.
Source Code #
Mechanism: The “Dual Mode” AQS #
ReentrantReadWriteLock is built on AbstractQueuedSynchronizer (AQS), but it uses the 32-bit state variable in an interesting way:
- Low 16 bits: Represent the exclusive (write) hold count.
- High 16 bits: Represent the shared (read) hold count.
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
When a thread tries to acquire the read lock, it checks the exclusive (write) count. If it is 0 (or held by the current thread), it increments the shared (read) count via CAS. If a thread tries to acquire the write lock, it checks both counts and must ensure both are 0 (or held by the current thread).
Canonical Usage #
When to use: Use ReentrantReadWriteLock when reads vastly outnumber writes and when you want to improve scalability by allowing multiple concurrent readers. It is ideal for read-mostly caches or shared configuration data structures.
Common Patterns:
- Read-Mostly Cache: Protecting a collection where many threads read data but updates are infrequent.
- Optimistic Locking Support: Using
tryReadLockto check for updates without blocking.
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Map<String, Data> cache = new HashMap<>();
public Data get(String key) {
rwLock.readLock().lock(); // Multiple readers can lock concurrently
try {
return cache.get(key);
} finally {
rwLock.readLock().unlock();
}
}
public void put(String key, Data val) {
rwLock.writeLock().lock(); // Exclusive lock (no readers allowed)
try {
cache.put(key, val);
} finally {
rwLock.writeLock().unlock();
}
}
Performance Considerations #
- Pros: Outstanding scalability for read-heavy workloads. No read-read contention.
- Cons: Write-lock starvation is possible if many readers arrive continuously. Complexity in the implementation leads to higher overhead than
ReentrantLockfor single-threaded or write-heavy scenarios.