Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why isn't ReadWriteLock upgrade allowed?

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();
    }
}
like image 646
Bass Avatar asked Oct 26 '15 09:10

Bass


1 Answers

First, let's note that upgrade and downgrade are not equivalent in terms of semantical complexity for ReadWriteLocks.

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:

  • under low contention its optimistic reads are much faster than using read locks
  • its API is far less restrictive about upgrading (from optimistic reading to read lock to write lock)
  • my various attempts to create a realistic JMH benchmark where one of the other similar locks beats it have almost always failed
like image 74
Dimitar Dimitrov Avatar answered Sep 20 '22 08:09

Dimitar Dimitrov