ReadWriteLock
downgrade is allowed by ReentrantReadWriteLock
implementation (tryLock()
from the example below always returns true
):
void downgrade(final ReadWriteLock readWriteLock) {
boolean downgraded = false;
readWriteLock.writeLock().lock();
try {
// Always true, as we already hold a W lock.
final boolean readLockAcquired = readWriteLock.readLock().tryLock();
if (readLockAcquired) {
// Now holding both a R and a W lock.
assert ((ReentrantReadWriteLock) readWriteLock).getReadHoldCount() == 1;
assert ((ReentrantReadWriteLock) readWriteLock).getWriteHoldCount() == 1;
readWriteLock.writeLock().unlock();
downgraded = true;
try {
// Now do some work with only a R lock held
} finally {
readWriteLock.readLock().unlock();
assert ((ReentrantReadWriteLock) readWriteLock).getReadHoldCount() == 0;
assert ((ReentrantReadWriteLock) readWriteLock).getWriteHoldCount() == 0;
}
}
} finally {
if (!downgraded) {
// Never (we were holding a W lock while trying a R lock).
readWriteLock.writeLock().unlock();
}
assert ((ReentrantReadWriteLock) readWriteLock).getReadHoldCount() == 0;
assert ((ReentrantReadWriteLock) readWriteLock).getWriteHoldCount() == 0;
}
}
What was the idea behind not allowing a lock upgrade in a similar manner? The tryLock()
method for a Write lock below could safely return true
w/o a risk for a deadlock in the absence of other threads holding a Read lock:
void upgrade(final ReadWriteLock readWriteLock) {
readWriteLock.readLock().lock();
try {
// Always false: lock upgrade is not allowed
final boolean writeLockAcquired = readWriteLock.writeLock().tryLock();
// ...
} finally {
readWriteLock.readLock().unlock();
}
}
First, let's note that upgrade and downgrade are not equivalent in terms of semantical complexity for ReadWriteLock
s.
You don't need to fend off contention to complete a downgrade transaction, because you are already holding the most escalated privileges on the lock, and because you are guaranteed to be the sole thread currently performing a downgrade. The same is not true for upgrade, so the mechanism that backs upgrade naturally needs to be more complex (or much smarter).
To be usable, the upgrade mechanism needs to prevent deadlocks in case two reading threads simultaneously try to upgrade (or specifically for ReentrantReadWriteLock
, in case a single reading thread holding multiple read locks tries to upgrade). In addition, the mechanism needs to specify how would the failed upgrade request be handled (will its read lock be invalidated) and that's even less trivial.
As you probably see by now, fully handling these problems in ReentrantReadWriteLock
is inconvenient to say the least (still, this is what .NET's ReaderWriterLock
tries and I think actually succeeds to do). My guess is that while final boolean writeLockAcquired = readWriteLock.writeLock().tryLock();
could have been made to succeed in some trivial cases, the upgradeability still wouldn't have been good enough for common use - under heavy enough contention, if you lose the race for the write lock, you are in the same boat as if you unlocked the read lock and tried to obtain the write lock (leaving opportunity for someone else to sneak in and take the write lock in-between).
A nice way to provide lock upgradeability is to allow only a single thread to try to upgrade - this is what ReentrantReadWriteUpdateLock
does or what .NET's ReaderWriterLockSlim
do. However I would still recommend Java 8's StampedLock
as: