我现在在代码中使用ReentrantReadWriteLock来同步树状结构的访问。这种结构很大,可以被许多线程同时读取,偶尔对其一部分进行修改,因此似乎很适合读写习惯。我了解到,对于这一特定类,不能将读取锁提升为写入锁,因此根据Javadocs,必须在获得写入锁之前释放读取锁。之前,我已经在非可重入上下文中成功使用了这种模式。
但是,我发现我无法可靠地获取写锁而不会永远阻塞。由于读锁是可重入的,而我实际上是这样使用的,因此简单的代码
lock.getReadLock().unlock(); lock.getWriteLock().lock()
如果我重新获得了readlock,则可以阻止。每次解锁请求只会减少保留计数,并且只有在保留计数达到零时才实际释放锁定。
编辑 以澄清这一点,因为我认为我一开始解释得不太好- 我知道此类中没有内置的锁升级,而且我必须简单地释放读取锁并获得写入锁。我的问题是/无论其他线程在做什么,如果重新获得了该调用,则调用getReadLock().unlock()实际上可能不会释放 该 线程的锁定,在这种情况下,getWriteLock().lock()由于该线程仍处于读取保持状态,因此对它的调用将永远阻塞锁定并因此阻止自身。
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实现是专为可重入设计的,因此,当可以重新获得锁时,肯定有某种明智的方法可以将读锁提升为写锁?这是至关重要的部分,这意味着幼稚的方法行不通。
我在这方面取得了一些进展。通过显式地将lock变量声明为a ReentrantReadWriteLock而不是简单地声明ReadWriteLock(不是理想值,但在这种情况下可能是必要的弊端),我可以调用该getReadHoldCount()方法。这使我可以获得当前线程的保持次数,因此可以多次释放该readlock(然后在以后重新获得相同的次数)。如此工作,如快速和肮脏的测试所示:
ReentrantReadWriteLock
ReadWriteLock
getReadHoldCount()
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(); }
不过,这将是我能做的最好的事情吗?感觉不太优雅,我仍然希望有一种方法可以减少“手动”方式的处理。