我目前在我的代码中使用 ReentrantReadWriteLock 来同步对树状结构的访问。这个结构很大,可以同时被许多线程读取,偶尔会修改其中的一小部分——所以它似乎很适合读写惯用语。我知道对于这个特定的类,不能将读锁提升为写锁,因此根据 Javadocs,必须在获得写锁之前释放读锁。我以前在非重入上下文中成功使用过这种模式。
然而,我发现的是,我无法在不永远阻塞的情况下可靠地获取写锁。由于读锁是可重入的,我实际上是这样使用它的,简单的代码
lock.getReadLock().unlock();
lock.getWriteLock().lock()
如果我以可重入方式获取了读锁,则可以阻止。每次调用 unlock 只会减少保持计数,只有当保持计数为零时才会真正释放锁。
编辑 以澄清这一点,因为我认为我最初解释得不太好——我知道这个类中没有内置的锁升级,我必须简单地释放读锁并获得写锁。我的问题是/是无论其他线程在做什么,调用 getReadLock().unlock()
如果它重新获取它,实际上可能不会释放 该 线程对锁的持有,在这种情况下调用 getWriteLock().lock()
将永远阻塞,因为该线程仍然持有读锁并因此阻塞自身。
例如,此代码片段永远不会到达 println 语句,即使在没有其他线程访问锁的单线程运行时也是如此:
final ReadWriteLock lock = new ReentrantReadWriteLock();
lock.getReadLock().lock();
// In real code we would go call other methods that end up calling back and
// thus locking again
lock.getReadLock().lock();
// Now we do some stuff and realise we need to write so try to escalate the
// lock as per the Javadocs and the above description
lock.getReadLock().unlock(); // Does not actually release the lock
lock.getWriteLock().lock(); // Blocks as some thread (this one!) holds read lock
System.out.println("Will never get here");
所以我问,有没有一个很好的成语来处理这种情况?具体来说,当持有读锁的线程(可能是可重入的)发现它需要做一些写操作,因此想要“挂起”它自己的读锁以获取写锁(根据其他线程的需要阻塞)释放他们对读锁的持有),然后在相同的状态下“拿起”它对读锁的持有?
由于此 ReadWriteLock 实现是专门为可重入而设计的,因此当可能以可重入方式获取锁时,肯定有一些明智的方法将读锁提升为写锁吗?这是关键部分,意味着天真方法不起作用。
原文由 Andrzej Doyle 发布,翻译遵循 CC BY-SA 4.0 许可协议
我在这方面取得了一些进展。通过将锁定变量显式声明为
ReentrantReadWriteLock
而不是简单的ReadWriteLock
(不太理想,但在这种情况下可能是必要的邪恶)我可以调用getReadHoldCount()
方法。这让我获得了当前线程的保持数,因此我可以多次释放读锁(并在之后重新获取相同的数字)。所以这是有效的,如快速和肮脏的测试所示:不过,这会是我能做的最好的吗?它感觉不是很优雅,我仍然希望有一种方法可以以较少的“手动”方式处理这个问题。