为什么不允许ReadWriteLock升级?

时间:2015-10-26 09:43:12

标签: java concurrency reentrantlock reentrantreadwritelock

ReadWriteLock实施允许

ReentrantReadWriteLock降级(以下示例中的tryLock()始终返回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()方法可以安全地返回true,如果没有其他线程持有 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();
    }
}

1 个答案:

答案 0 :(得分:3)

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

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

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

正如您现在可能已经看到的那样,在ReentrantReadWriteLock中完全处理这些问题是不方便的,至少可以说(这仍然是.NET ReaderWriterLock尝试的,我认为实际上是成功的做)。我的猜测是虽然final boolean writeLockAcquired = readWriteLock.writeLock().tryLock();本可以在一些微不足道的情况下取得成功,但升级性仍然不足以满足普通使用 - 在足够严重的争用下,如果你失去了写作的竞争锁定,你在同一条船上,好像你解锁了读锁定并试图获得写锁定(让其他人潜入并获取其间的写锁定的机会)。

提供锁升级性的一个好方法是只允许一个线程尝试升级 - 这是ReentrantReadWriteUpdateLock做的或者ReaderWriterLockSlim做的事情。但是我仍然会推荐Java 8' StampedLock作为:

  • 在低争用下,其乐观读取比使用读锁快得多
  • 它的API对升级的限制要少得多(从乐观读取到读锁定到写锁定)
  • 我尝试创建一个真实的JMH基准测试,其中一个类似的锁定它几乎总是失败