目前我的代码中正在使用重入读写锁(ReentrantReadWriteLock)来同步访问类似树形结构的数据。这个结构很大,被许多线程同时读取,并且偶尔对其进行小部分修改 - 因此似乎很适合使用读写模式。我知道使用这个特定的类,无法将读锁升级为写锁,因此根据Javadocs,在获取写锁之前必须释放读锁。我以前在非可重入环境中成功地使用过这种模式。
然而我发现,我无法可靠地获取写锁而不会一直阻塞。由于读锁是可重入的,我实际上正在使用它作为这样的锁,简单的代码
lock.getReadLock().unlock();
lock.getWriteLock().lock()
如果我已经重入地获取了读锁,那么可能会出现阻塞。每次调用 unlock 只会减少 hold count,只有当 hold count 减为零时,锁才会被实际释放。
编辑 为了澄清这一点,因为我认为我最初没有解释得太好 - 我知道在这个类中没有内置的锁升级,我必须简单地释放读锁并获取写锁。我的问题是/曾经是,无论其他线程正在做什么,调用 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实现专门设计为可重入,因此肯定存在一些合理的方法可以将读锁提升为写锁,即当可以重新获取锁时。这是关键部分,这意味着天真的方法行不通。