3

考虑这个JDK标准接口:

public interface ReadWriteLock{
    public Lock readLock();
    public Lock writeLock();
}

B. Goetz in Java Concurrency 在实践中提到从 readLock 升级到 writeLock 容易死锁。

如果两个读取器同时尝试升级到写锁,则两者都不会重新获得读锁。

让我感到困惑的是,有两个读者试图升级。但即使是一个读者就足够了,不是吗?如果读者尝试升级它还没有释放读锁。试图在持有读锁的情况下获取写锁是死锁的。

因此,据此判断,我认为提供升级操作在理论上甚至是荒谬的。或者也许一个实现可以解决这个问题?

4

1 回答 1

7

听起来您正在考虑将“读锁”和“写锁”视为构成读写锁的两个不同的锁。这不是正确的思考方式,即使 API 似乎公开了这样的组合(通过提供方法来获取对“写锁”和“读锁”的引用)。

(这一切都被术语“锁”的重载弄糊涂了——它既是动词又是名词,也就是说,可以锁定一把)。

ReadWriteLock与其认为'readLock方法和方法返回实际锁,不如writeLock考虑它们实际所做的是返回一个抽象机制,允许获取不同类型的锁(在相同的、单一的、读写锁机制上)。

读写锁(机制)是一个单一的锁,可以通过两种方式锁定:read-lockedwrite-locked。读写锁可以被一个或多个线程并发读锁,也可以写锁。它永远不能同时被读锁定和写锁定。

如果读者尝试升级它还没有释放读锁。试图在持有读锁的情况下获取写锁是死锁的。

写锁比读锁强;它就像一个带有附加属性的读锁(没有其他线程也可以持有该锁)。从读锁升级到写锁并不是要在已经持有另一个锁时获取一个锁;相反,它是关于更改已经在单个锁上持有的锁的类型。

因此,单线程将其读锁升级为写锁不存在概念上的问题;它只需要等到读锁的所有其他持有者都放弃它,然后才能进行升级。在这种情况下不存在死锁的可能性。

例如,假设有三个线程 - A、B 和 C,它们都已读锁定了锁。线程 A 尝试升级到写锁。这意味着它必须等待 B 和 C 放弃他们的读锁。这最终会发生,此时线程 A 获得了一个写锁(最终,线程 A 将放弃这个锁)。

另一方面,考虑 A 和 B 是否都尝试升级到写锁。这意味着他们都在等待对方放弃读锁,这不会发生;对于线程 A 放弃读锁,它首先需要获取写锁,直到线程 B 放弃读锁才会发生,直到它获得写锁才会发生,这不会发生直到线程 A 放弃读锁……等等。有一个僵局。

Java API 不允许从读锁升级到写锁,因为这样可以防止死锁情况。编写一个可以安全升级锁类型的程序是可能的(如果有点棘手),但 Java API 无论如何都不允许这样做。

于 2016-05-07T10:17:36.210 回答