Skip to main content
  1. Java Concurrency (java.util.concurrent)/

ReentrantReadWriteLock

2 mins

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 #

View Source on GitHub

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 tryReadLock to 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 ReentrantLock for single-threaded or write-heavy scenarios.