为什么不允许 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 基准测试所做的各种尝试几乎总是失败
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 基准测试所做的各种尝试几乎总是失败