Definition
- Downgrade – while holding the write lock, acquire the read lock, then release the write lock; moves from exclusive to shared access.
- Safe – because the write lock is exclusive, no one else holds any lock, so the read lock succeeds immediately.
- Upgrade forbidden – if two readers both try to upgrade, each waits for the other to release its read lock, causing deadlock.
- Race window – without downgrading, releasing write then acquiring read separately lets another writer intervene.
- Workaround – for upgrade: release read, acquire write, re-check the condition.
Picture the National Bank of India master interest-rate sheet. The write lock is locking yourself alone in the records room. Downgrading is propping the door open to reading mode after changing the rate, so you read the exact value you just wrote. Upgrading is two auditors both reading in the room both wanting to become the sole editor: each refuses to leave until the other leaves, so they stand forever.
Understanding the Concepts
What is lock downgrading?
A ReentrantReadWriteLock exposes readLock() (shared, multiple readers) and writeLock() (exclusive, one writer). The holder of the write lock can also acquire the read lock because the lock is reentrant and the write lock implies exclusive access.
How do you downgrade safely?
The key is to acquire the read lock before releasing the write lock. This guarantees continuous protection with no gap.
package com.bank.treasury;
import java.math.BigDecimal;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class InterestRateService {
private final ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
private BigDecimal rate = new BigDecimal("0.065");
public BigDecimal updateAndApplyRate(BigDecimal newRate, BigDecimal principal) {
rw.writeLock().lock(); // Step 1: exclusive access
try {
rate = newRate; // Step 2: WRITE the new rate
rw.readLock().lock(); // Step 3: acquire read BEFORE releasing write
} finally {
rw.writeLock().unlock(); // Step 4: release write lock (downgrade complete)
}
try {
// Step 5: read the fresh value under read lock protection
return principal.multiply(rate);
} finally {
rw.readLock().unlock(); // Step 6: release read lock
}
}
}
Why is upgrading forbidden?
If a thread holds the read lock and calls writeLock().lock(), it blocks because the write lock needs exclusivity, but the thread itself still holds a read lock that prevents exclusivity. With two threads, both upgrading creates a deadlock.
// DEADLOCK SCENARIO:
// Thread A: readLock held → writeLock.lock() → blocks (waiting for B's read)
// Thread B: readLock held → writeLock.lock() → blocks (waiting for A's read)
// Neither can release their read lock because they are both stuck in writeLock.lock()
rw.readLock().lock();
try {
rw.writeLock().lock(); // BLOCKS FOREVER: waiting for own read lock release
} finally { /* never reached */ }
What is the upgrade workaround?
Release the read lock, acquire the write lock, and re-check the condition because the state may have changed during the gap.
package com.bank.treasury;
import java.math.BigDecimal;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ConditionalRateUpdate {
private final ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
private BigDecimal rate = new BigDecimal("0.065");
public BigDecimal conditionalUpdate(BigDecimal newRate) {
rw.readLock().lock();
try {
if (needsUpdate(rate, newRate)) {
rw.readLock().unlock(); // release read first
rw.writeLock().lock(); // acquire write (gap exists here!)
try {
if (needsUpdate(rate, newRate)) { // RE-CHECK: state may have changed
rate = newRate;
}
rw.readLock().lock(); // downgrade to read
} finally {
rw.writeLock().unlock(); // now only read lock held
}
}
return rate;
} finally {
rw.readLock().unlock();
}
}
private boolean needsUpdate(BigDecimal current, BigDecimal proposed) {
return current.compareTo(proposed) != 0;
}
}
Why is downgrading useful?
It closes the race window. Without downgrading, releasing write then separately acquiring read leaves a gap where another writer can change the value.
| Approach | Race Window? | Continuity? |
|----------|-------------|-------------|
| Downgrade (read before releasing write) | No gap | Read the exact value you wrote |
| Release write, then acquire read | Gap exists | Another writer may intervene |
How does StampedLock improve on this?
StampedLock (Java 8) provides tryConvertToReadLock(stamp) and tryConvertToWriteLock(stamp) methods that attempt atomic conversion. If the conversion fails, you fall back to the release-and-reacquire pattern.
StampedLock sl = new StampedLock();
long stamp = sl.writeLock();
try {
rate = newRate;
long readStamp = sl.tryConvertToReadLock(stamp); // atomic downgrade
if (readStamp != 0L) {
stamp = readStamp; // downgrade succeeded
} else {
sl.unlockWrite(stamp); // fall back: release write, acquire read
stamp = sl.readLock();
}
return principal.multiply(rate);
} finally {
sl.unlock(stamp);
}
What are common interview follow-up questions?
- How does StampedLock handle optimistic reads and upgrades? Optimistic read uses
tryOptimisticRead()which returns a stamp without acquiring any lock. Validate withvalidate(stamp)before using the data. - Can you downgrade from a StampedLock write stamp to a read stamp? Yes, with
tryConvertToReadLock(writeStamp). - What pattern replaces upgrade in practice? The release-recheck-acquire pattern shown above is the standard workaround.
Visual Overview
Downgrade (Safe): ┌───────────────────────────────────────┐ │ 1. writeLock().lock() [exclusive] │ │ 2. mutate state │ │ 3. readLock().lock() [still excl] │ │ 4. writeLock().unlock() [shared only] │ │ 5. read derived value [protected] │ │ 6. readLock().unlock() [released] │ └───────────────────────────────────────┘ Upgrade (FORBIDDEN): Thread A: readLock held → writeLock.lock() → BLOCKED (own read) Thread B: readLock held → writeLock.lock() → BLOCKED (A's read) Result: DEADLOCK Workaround: readLock.unlock() → writeLock.lock() → re-check → readLock.lock() → writeLock.unlock()
Key Takeaway
Lock downgrading is the safe pattern where a thread holding the write lock acquires the read lock before releasing the write lock, moving from exclusive to shared access with no race window. Upgrading (read to write) is forbidden because two readers both upgrading deadlock.
At National Bank of India, treasury used downgrading to update the interest rate and compute the derived daily factor from the exact new value, guaranteeing no other writer slipped in between.
Remember one sentence: downgrade by taking the read lock before releasing the write lock, and never upgrade read to write because two readers both upgrading will deadlock.
---