⚡ Advanced Concurrency · Advanced

ReadWriteLock & StampedLock in Java

Many readers or one writer; optimistic reads.

🧩 The mysteryA config cache is read 10,000 times a second and refreshed once a minute, yet every reader queues behind a single lock. Readers can't hurt each other, so why make them wait?

Many readers OR one writer

A ReentrantReadWriteLock has two locks. The read lock is shared: any number of readers can hold it at once, never blocking each other. The write lock is exclusive: no readers and no other writers. Readers only need to exclude writers.

var rw = new ReentrantReadWriteLock();
 
rw.readLock().lock();     // many at once
try { return cache.get(k); }
finally { rw.readLock().unlock(); }
🔮 Predict it

Your turn

main holds the write lock. Can it also take the read lock?

var rw = new ReentrantReadWriteLock();
rw.writeLock().lock();
System.out.println(rw.readLock().tryLock());
  1. true
  2. false
  3. Throws IllegalMonitorStateException
Show the answer

true. The writer already excludes everyone else, so it may also take the read lock. That's how you downgrade: acquire read, then release write. The opposite direction is the trap on the next screen.

⚠️ The trap

No upgrades

Holding a read lock, you can't upgrade to the write lock. writeLock().tryLock() returns false, and writeLock().lock() would wait forever for readers to leave, including yourself. Release the read lock first; once it's free, the write lock can be taken.

rw.readLock().lock();
rw.writeLock().tryLock();  // false!
rw.readLock().unlock();
rw.writeLock().tryLock();  // true

StampedLock: optimistic reads

StampedLock adds a cheaper path. tryOptimisticRead() returns a stamp without locking. Read your fields, then validate(stamp): true means no write happened since. If false, fall back to a real read lock.

long s = sl.tryOptimisticRead();
double cx = x, cy = y;
if (!sl.validate(s)) {
    s = sl.readLock();
    try { cx = x; cy = y; }
    finally { sl.unlockRead(s); }
}
🤔 Think first

Is the stamp still good?

You get a stamp from tryOptimisticRead(). Another thread then acquires and releases the write lock. What does validate(stamp) return?

Think about it, then reveal the answer

false. Any write-lock acquisition after the stamp invalidates it, even if that write lock has already been released. That's the signal to re-read your data under a real read lock.

StampedLock is not reentrant

Unlike ReentrantReadWriteLock, StampedLock isn't reentrant: a thread that already holds its write lock and asks again blocks itself forever. It also has no Conditions. Keep its critical sections small and simple.

💼 In the real world

Measure first

Read-write locks pay off only when reads vastly outnumber writes: config caches, routing tables, reference data. With balanced traffic, their bookkeeping can make them slower than a plain lock. Profile first, then switch.

Key takeaways

  1. Read lock: shared. Write lock: exclusive
  2. Pays off only when reads vastly outnumber writes
  3. tryOptimisticRead() + validate(stamp) avoids locking on reads
  4. StampedLock isn't reentrant — re-locking can deadlock yourself
🤯 Did you know?

StampedLock's own javadoc demonstrates optimistic reading with a tiny 2D Point class and a distanceFromOrigin() method.

Practice questions

What does this print?

var rw = new ReentrantReadWriteLock();
rw.readLock().lock();
System.out.println(rw.writeLock().tryLock());
rw.readLock().unlock();
System.out.println(rw.writeLock().tryLock());
  1. false true
  2. true true
  3. true false
  4. false false
Check your answer

false true. You can't upgrade a held read lock to a write lock, so the first tryLock() fails. Once the read lock is released, the write lock is free.

What does this print?

var sl = new StampedLock();
long s = sl.tryOptimisticRead();
System.out.println(sl.validate(s));
long w = sl.writeLock();
sl.unlockWrite(w);
System.out.println(sl.validate(s));
  1. true false
  2. true true
  3. false false
  4. false true
Check your answer

true false. The stamp stays valid until a write lock is acquired. After the write, validate(s) is false, telling the reader to retry under a real read lock.

Next: the map every concurrent Java app relies on. ConcurrentHashMap: lock-free reads, atomic merges, and a strict no-nulls policy.