Java ReentrantReadWriteLocks - 如何在读锁中安全地获取写锁? [英] Java ReentrantReadWriteLocks - how to safely acquire write lock when in a read lock?

查看:18
本文介绍了Java ReentrantReadWriteLocks - 如何在读锁中安全地获取写锁?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我现在在我的代码中使用 ReentrantReadWriteLock 同步对树状结构的访问.这个结构很大,可以同时被多个线程读取,偶尔会修改它的小部分——所以它似乎很适合读写习惯用法.我知道对于这个特定的类,不能将读锁提升为写锁,因此根据 Javadoc,必须在获得写锁之前释放读锁.我以前在不可重入的上下文中成功地使用了这种模式.

I am using in my code at the moment a ReentrantReadWriteLock to synchronize access over a tree-like structure. This structure is large, and read by many threads at once with occasional modifications to small parts of it - so it seems to fit the read-write idiom well. I understand that with this particular class, one cannot elevate a read lock to a write lock, so as per the Javadocs one must release the read lock before obtaining the write lock. I've used this pattern successfully in non-reentrant contexts before.

然而,我发现我无法在不永远阻塞的情况下可靠地获取写锁.由于读锁是可重入的,而且我实际上就是这样使用它的,简单的代码

What I'm finding however is that I cannot reliably acquire the write lock without blocking forever. Since the read lock is reentrant and I am actually using it as such, the simple code

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

如果我以可重入的方式获取了读锁,则可以阻止.每次调用 unlock 只会减少保持计数,并且只有当保持计数为零时才会真正释放锁.

can block if I have acquired the readlock reentrantly. Each call to unlock just reduces the hold count, and the lock is only actually released when the hold count hits zero.

EDIT 澄清这一点,因为我认为我最初解释得不太好 - 我知道这个类中没有内置的锁升级,我必须简单地释放读锁,获得写锁.我的问题是,不管其他线程在做什么,调用 getReadLock().unlock() 可能实际上不会释放 这个 线程对锁的持有,如果它获得了它可重入,在这种情况下,对 getWriteLock().lock() 的调用将永远阻塞,因为该线程仍然持有读锁并因此阻塞自身.

EDIT to clarify this, as I don't think I explained it too well initially - I am aware that there is no built-in lock escalation in this class, and that I have to simply release the read lock and obtain the write lock. My problem is/was that regardless of what other threads are doing, calling getReadLock().unlock() may not actually release this thread's hold on the lock if it acquired it reentrantly, in which case the call to getWriteLock().lock() will block forever as this thread still has a hold on the read lock and thus blocks itself.

例如,即使在没有其他线程访问锁的情况下单线程运行时,此代码片段也永远不会到达 println 语句:

For example, this code snippet will never reach the println statement, even when run singlethreaded with no other threads accessing the lock:

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");

所以我问,有没有很好的成语来处理这种情况?具体来说,当一个持有读锁(可能是可重入的)的线程发现它需要做一些写操作时,因此想要挂起"自己的读锁以获取写锁(在其他线程上按要求阻塞)释放他们对读锁的持有),然后在相同的状态下拿起"它对读锁的持有?

So I ask, is there a nice idiom to handle this situation? Specifically, when a thread that holds a read lock (possibly reentrantly) discovers that it needs to do some writing, and thus wants to "suspend" its own read lock in order to pick up the write lock (blocking as required on other threads to release their holds on the read lock), and then "pick up" its hold on the read lock in the same state afterwards?

既然这个 ReadWriteLock 实现是专门设计为可重入的,那么当可以重入获取锁时,肯定有一些明智的方法可以将读锁提升为写锁吗?这是关键部分,这意味着幼稚的方法不起作用.

Since this ReadWriteLock implementation was specifically designed to be reentrant, surely there is some sensible way to elevate a read lock to a write lock when the locks may be acquired reentrantly? This is the critical part that means the naive approach does not work.

推荐答案

我在这方面取得了一些进展.通过将锁变量显式声明为 ReentrantReadWriteLock 而不是简单的 ReadWriteLock(不太理想,但在这种情况下可能是必要的邪恶)我可以调用 getReadHoldCount() 方法.这让我可以获得当前线程的保留数量,因此我可以多次释放读锁(然后重新获取相同的数量).所以这是有效的,如快速和肮脏的测试所示:

I have made a little progress on this. By declaring the lock variable explicitly as a ReentrantReadWriteLock instead of simply a ReadWriteLock (less than ideal, but probably a necessary evil in this case) I can call the getReadHoldCount() method. This lets me obtain the number of holds for the current thread, and thus I can release the readlock this many times (and reacquire it the same number afterwards). So this works, as shown by a quick-and-dirty test:

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();
}

不过,这会是我能做的最好的吗?感觉不是很优雅,我仍然希望有一种方法可以在不那么手动"的情况下处理这个问题.时尚.

Still, is this going to be the best I can do? It doesn't feel very elegant, and I'm still hoping that there's a way to handle this in a less "manual" fashion.

这篇关于Java ReentrantReadWriteLocks - 如何在读锁中安全地获取写锁?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆