小编典典

Java ReentrantReadWriteLocks-如何安全获取写锁?

java

我现在在代码中使用ReentrantReadWriteLock来同步树状结构的访问。这种结构很大,可以被许多线程同时读取,偶尔对其一部分进行修改,因此似乎很适合读写习惯。我了解到,对于这一特定类,不能将读取锁提升为写入锁,因此根据Javadocs,必须在获得写入锁之前释放读取锁。之前,我已经在非可重入上下文中成功使用了这种模式。

但是,我发现我无法可靠地获取写锁而不会永远阻塞。由于读锁是可重入的,而我实际上是这样使用的,因此简单的代码

lock.getReadLock().unlock();
lock.getWriteLock().lock()

如果我重新获得了readlock,则可以阻止。每次解锁请求只会减少保留计数,并且只有在保留计数达到零时才实际释放锁定。

编辑 以澄清这一点,因为我认为我一开始解释得不太好-
我知道此类中没有内置的锁升级,而且我必须简单地释放读取锁并获得写入锁。我的问题是/无论其他线程在做什么,如果重新获得了该调用,则调用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实现是专为可重入设计的,因此,当可以重新获得锁时,肯定有某种明智的方法可以将读锁提升为写锁?这是至关重要的部分,这意味着幼稚的方法行不通。


阅读 211

收藏
2020-09-11

共1个答案

小编典典

我在这方面取得了一些进展。通过显式地将lock变量声明为a
ReentrantReadWriteLock而不是简单地声明ReadWriteLock(不是理想值,但在这种情况下可能是必要的弊端),我可以调用该getReadHoldCount()方法。这使我可以获得当前线程的保持次数,因此可以多次释放该readlock(然后在以后重新获得相同的次数)。如此工作,如快速和肮脏的测试所示:

final int holdCount = lock.getReadHoldCount();
for (int i = 0; i < holdCount; i++) {
   lock.readLock().unlock();
}
lock.writeLock().lock();
try {
   // Perform modifications
} finally {
   // Downgrade by reacquiring read lock before releasing write lock
   for (int i = 0; i < holdCount; i++) {
      lock.readLock().lock();
   }
   lock.writeLock().unlock();
}

不过,这将是我能做的最好的事情吗?感觉不太优雅,我仍然希望有一种方法可以减少“手动”方式的处理。

2020-09-11