为什么不允许 ReadWriteLock 升级?

Why isn't ReadWriteLock upgrade allowed?

ReadWriteLock 降级被 ReentrantReadWriteLock 实施所允许(tryLock() 从下面的例子总是 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;
    }
}

不允许以类似方式升级锁背后的想法是什么?下面的 Write 锁的 tryLock() 方法可以安全地 return true w/o 在没有其他线程的情况下出现死锁的风险持有 Read 锁:

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();
    }
}

首先,请注意升级和降级在 ReadWriteLock 的语义复杂度方面并不等同。

您不需要抵制争用来完成降级事务,因为您已经拥有对锁的最高升级权限,并且因为您保证是当前执行降级的唯一线程。升级不一样,所以支持升级的机制自然需要更复杂(或更智能)。

为了可用,升级机制需要防止死锁,以防两个读取线程同时尝试升级(或专门针对 ReentrantReadWriteLock,以防持有多个读取锁的单个读取线程尝试升级)。此外,该机制需要指定如何处理失败的升级请求(其读锁是否会失效),这就更不重要了。

正如您现在可能看到的那样,在 ReentrantReadWriteLock 中完全处理这些问题至少可以说是不方便的(不过,这是 .NET 的 ReaderWriterLock 尝试的,我认为实际上成功了) .我的猜测是,虽然 final boolean writeLockAcquired = readWriteLock.writeLock().tryLock(); 可以在一些微不足道的情况下取得成功,但可升级性仍然不够好以供普遍使用——在足够激烈的竞争下,如果你输掉了写锁的竞争,你在同一条船上,就好像你解锁了读锁并试图获得写锁(给其他人留下偷偷摸摸的机会并在两者之间获取写锁)。

提供锁可升级性的一个好方法是只允许一个线程尝试升级——这就是 ReentrantReadWriteUpdateLock does or what .NET's ReaderWriterLockSlim 所做的。但是我仍然会推荐 Java 8 的 StampedLock 作为:

  • 在低竞争下,其乐观读取比使用读取锁快得多
  • 其API对升级(从乐观读到读锁再到写锁)的限制要少得多
  • 我为创建一个现实的 JMH 基准测试所做的各种尝试几乎总是失败