Java ReentrantReadWriteLocks - 如何在读锁中安全地获取写锁?

发布于 2024-07-12 01:45:31 字数 1648 浏览 6 评论 0原文

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

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

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

如果我以可重入的方式获取了读锁,那么简单的代码可能会阻塞。 每次调用解锁只会减少保持计数,并且只有当保持计数为零时才会真正释放锁。

编辑来澄清这一点,因为我认为我最初解释得不太好 - 我知道这个类中没有内置的锁升级,并且我必须简单地释放读取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 实现是专门设计为可重入的,因此当可以重入获取锁时,肯定有某种明智的方法可以将读锁提升为写锁吗? 这是关键部分,意味着天真的方法不起作用。

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

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 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.

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?

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

梦幻的味道 2024-07-19 01:45:31

这是一个老问题,但这里既提供了问题的解决方案,也提供了一些背景信息。

正如其他人指出的那样,经典的读者-作者锁(例如JDK ReentrantReadWriteLock)本质上是不支持将读锁升级为写锁,因为这样做容易出现死锁。

如果您需要安全地获取写锁而不首先释放读锁,那么有一个更好的选择:看看读-写-更新锁。

我编写了一个 ReentrantReadWrite_Update_Lock,并在 此处下根据 Apache 2.0 许可证将其作为开源发布。 我还在 JSR166 concurrency-interest 邮件列表中发布了该方法的详细信息,并且该方法经受住了该名单上成员的反复审查。

该方法非常简单,正如我在并发兴趣中提到的,这个想法并不是全新的,因为它至少早在 2000 年就在 Linux 内核邮件列表上进行了讨论。此外 .Net 平台的 ReaderWriterLockSlim 也支持锁定升级。 到目前为止,这个概念还没有在 Java (AFAICT) 上有效地实现。

这个想法是除了锁和锁之外还提供更新锁。 更新锁是介于读锁和写锁之间的中间类型的锁。 与写锁一样,一次只有一个线程可以获得更新锁。 但与读锁一样,它允许对持有它的线程进行读访问,并同时允许对持有常规读锁的其他线程进行读访问。 关键特性是更新锁可以从只读状态升级为写锁,并且不易出现死锁,因为一次只有一个线程可以持有更新锁并能够进行升级。

这支持锁升级,此外,在具有“先读后写”访问模式的应用程序中,它比传统的读写锁更有效,因为它会在更短的时间内阻塞读取线程。

网站上提供了示例用法。 该库具有 100% 的测试覆盖率,并且位于 Maven 中心。

This is an old question, but here's both a solution to the problem, and some background information.

As others have pointed out, a classic readers-writer lock (like the JDK ReentrantReadWriteLock) inherently does not support upgrading a read lock to a write lock, because doing so is susceptible to deadlock.

If you need to safely acquire a write lock without first releasing a read lock, there is a however a better alternative: take a look at a read-write-update lock instead.

I've written a ReentrantReadWrite_Update_Lock, and released it as open source under an Apache 2.0 license here. I also posted details of the approach to the JSR166 concurrency-interest mailing list, and the approach survived some back and forth scrutiny by members on that list.

The approach is pretty simple, and as I mentioned on concurrency-interest, the idea is not entirely new as it was discussed on the Linux kernel mailing list at least as far back as the year 2000. Also the .Net platform's ReaderWriterLockSlim supports lock upgrade also. So effectively this concept had simply not been implemented on Java (AFAICT) until now.

The idea is to provide an update lock in addition to the read lock and the write lock. An update lock is an intermediate type of lock between a read lock and a write lock. Like the write lock, only one thread can acquire an update lock at a time. But like a read lock, it allows read access to the thread which holds it, and concurrently to other threads which hold regular read locks. The key feature is that the update lock can be upgraded from its read-only status, to a write lock, and this is not susceptible to deadlock because only one thread can hold an update lock and be in a position to upgrade at a time.

This supports lock upgrade, and furthermore it is more efficient than a conventional readers-writer lock in applications with read-before-write access patterns, because it blocks reading threads for shorter periods of time.

Example usage is provided on the site. The library has 100% test coverage and is in Maven central.

漫漫岁月 2024-07-19 01:45:31

我在这方面已经取得了一些进展。 通过将锁定变量显式声明为 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();
}

但这将是我能做的最好的事情吗? 它感觉不太优雅,我仍然希望有一种方法可以以不那么“手动”的方式来处理这个问题。

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.

樱娆 2024-07-19 01:45:31

你想做的事情应该是可能的。 问题是Java没有提供可以将读锁升级为写锁的实现。 具体来说,javadoc ReentrantReadWriteLock 表示它不允许从读锁升级到写锁。

无论如何,Jakob Jenkov 描述了如何实现它。 请参阅http://tutorials.jenkov.com/java-concurrency/read-write-locks .html#upgrade 了解详细信息。

为什么需要升级读锁到写锁

从读锁升级到写锁是有效的(尽管其他答案中有相反的断言)。 可能会发生死锁,因此实现的一部分是识别死锁的代码,并通过在线程中抛出异常来打破死锁来打破死锁。 这意味着作为事务的一部分,您必须处理 DeadlockException,例如,通过重新执行该工作。 典型的模式是:

boolean repeat;
do {
  repeat = false;
  try {
   readSomeStuff();
   writeSomeStuff();
   maybeReadSomeMoreStuff();
  } catch (DeadlockException) {
   repeat = true;
  }
} while (repeat);

如果没有这种能力,实现连续读取一堆数据然后根据读取的内容写入内容的可序列化事务的唯一方法是在开始之前预期需要写入,从而获得 WRITE 锁在写入需要写入的内容之前读取的所有数据。 这是 Oracle 使用的 KLUDGE (SELECT FOR UPDATE ...)。 此外,它实际上减少了并发性,因为在事务运行时没有其他人可以读取或写入任何数据!

特别是,在获取写锁之前释放读锁会产生不一致的结果。 考虑一下:

int x = someMethod();
y.writeLock().lock();
y.setValue(x);
y.writeLock().unlock();

您必须知道 someMethod() 或其调用的任何方法是否在 y 上创建可重入读锁! 假设你知道它确实如此。 那么如果你先释放读锁:

int x = someMethod();
y.readLock().unlock();
// problem here!
y.writeLock().lock();
y.setValue(x);
y.writeLock().unlock();

另一个线程可能会在你释放它的读锁之后、在你获得它的写锁之前更改 y 。 所以 y 的值不会等于 x。

测试代码:将读锁升级为写锁块:

import java.util.*;
import java.util.concurrent.locks.*;

public class UpgradeTest {

    public static void main(String[] args) 
    {   
        System.out.println("read to write test");
        ReadWriteLock lock = new ReentrantReadWriteLock();

        lock.readLock().lock(); // get our own read lock
        lock.writeLock().lock(); // upgrade to write lock
        System.out.println("passed");
    }

}

使用 Java 1.6 输出:

read to write test
<blocks indefinitely>

What you want to do ought to be possible. The problem is that Java does not provide an implementation that can upgrade read locks to write locks. Specifically, the javadoc ReentrantReadWriteLock says it does not allow an upgrade from read lock to write lock.

In any case, Jakob Jenkov describes how to implement it. See http://tutorials.jenkov.com/java-concurrency/read-write-locks.html#upgrade for details.

Why Upgrading Read to Write Locks Is Needed

An upgrade from read to write lock is valid (despite the assertions to the contrary in other answers). A deadlock can occur, and so part of the implementation is code to recognize deadlocks and break them by throwing an exception in a thread to break the deadlock. That means that as part of your transaction, you must handle the DeadlockException, e.g., by doing the work over again. A typical pattern is:

boolean repeat;
do {
  repeat = false;
  try {
   readSomeStuff();
   writeSomeStuff();
   maybeReadSomeMoreStuff();
  } catch (DeadlockException) {
   repeat = true;
  }
} while (repeat);

Without this ability, the only way to implement a serializable transaction that reads a bunch of data consistently and then writes something based on what was read is to anticipate that writing will be necessary before you begin, and therefore obtain WRITE locks on all data that are read before writing what needs to be written. This is a KLUDGE that Oracle uses (SELECT FOR UPDATE ...). Furthermore, it actually reduces concurrency because nobody else can read or write any of the data while the transaction is running!

In particular, releasing the read lock before obtaining the write lock will produce inconsistent results. Consider:

int x = someMethod();
y.writeLock().lock();
y.setValue(x);
y.writeLock().unlock();

You have to know whether someMethod(), or any method it calls, creates a reentrant read lock on y! Suppose you know it does. Then if you release the read lock first:

int x = someMethod();
y.readLock().unlock();
// problem here!
y.writeLock().lock();
y.setValue(x);
y.writeLock().unlock();

another thread may change y after you release its read lock, and before you obtain the write lock on it. So y's value will not be equal to x.

Test Code: Upgrading a read lock to a write lock blocks:

import java.util.*;
import java.util.concurrent.locks.*;

public class UpgradeTest {

    public static void main(String[] args) 
    {   
        System.out.println("read to write test");
        ReadWriteLock lock = new ReentrantReadWriteLock();

        lock.readLock().lock(); // get our own read lock
        lock.writeLock().lock(); // upgrade to write lock
        System.out.println("passed");
    }

}

Output using Java 1.6:

read to write test
<blocks indefinitely>
骷髅 2024-07-19 01:45:31

你想要做的事情根本不可能以这种方式实现。

您不可能拥有可以毫无问题地从读升级到写的读/写锁。 示例:

void test() {
    lock.readLock().lock();
    ...
    if ( ... ) {
        lock.writeLock.lock();
        ...
        lock.writeLock.unlock();
    }
    lock.readLock().unlock();
}

现在假设有两个线程将进入该函数。 (并且您假设并发,对吧?否则您一开始就不会关心锁......)

假设两个线程将同时启动并且运行同样快嗯>。 这意味着,两者都会获得读锁,这是完全合法的。 然而,两个线程最终都会尝试获取写锁,但它们都不会获得:各自的其他线程持有读锁!

根据定义,允许将读锁升级为写锁的锁很容易出现死锁。 抱歉,但你需要修改你的方法。

What you are trying to do is simply not possible this way.

You cannot have a read/write lock that you can upgrade from read to write without problems. Example:

void test() {
    lock.readLock().lock();
    ...
    if ( ... ) {
        lock.writeLock.lock();
        ...
        lock.writeLock.unlock();
    }
    lock.readLock().unlock();
}

Now suppose, two threads would enter that function. (And you are assuming concurrency, right? Otherwise you would not care about locks in the first place....)

Assume both threads would start at the same time and run equally fast. That would mean, both would acquire a read lock, which is perfectly legal. However, then both would eventually try to acquire the write lock, which NONE of them will ever get: The respective other threads hold a read lock!

Locks that allow upgrading of read locks to write locks are prone to deadlocks by definition. Sorry, but you need to modify your approach.

快乐很简单 2024-07-19 01:45:31

Java 8 现在有一个 java.util.concurrent.locks.StampedLock
使用 tryConvertToWriteLock(long) API

更多信息请访问 http://www.tryConvertToWriteLock(long) API。 javaspecialists.eu/archive/Issue215.html

Java 8 now has a java.util.concurrent.locks.StampedLock
with a tryConvertToWriteLock(long) API

More info at http://www.javaspecialists.eu/archive/Issue215.html

梦中的蝴蝶 2024-07-19 01:45:31

您正在寻找的是锁升级,并且使用标准 java.concurrent ReentrantReadWriteLock 是不可能的(至少不是原子地)。 最好的办法是解锁/锁定,然后检查中间是否没有人进行修改。

您尝试执行的操作,强制所有读锁退出并不是一个好主意。 读锁的存在是有原因的,你不应该写。 :)

编辑:
正如 Ran Biron 指出的,如果你的问题是饥饿(读锁一直被设置和释放,永远不会降到零),你可以尝试使用公平队列。 但你的问题听起来不像是你的问题?

编辑2:
我现在看到你的问题,你实际上已经在堆栈上获得了多个读锁,并且你想将它们转换为写锁(升级)。 这对于 JDK 实现来说实际上是不可能的,因为它不跟踪读锁的所有者。 可能还有其他人持有您看不到的读锁,并且它不知道有多少读锁属于您的线程,更不用说您当前的调用堆栈(即您的循环正在杀死所有 读锁,而不仅仅是你自己的锁,所以你的写锁不会等待任何并发读者完成,你最终会手上一团糟)

我实际上也遇到过类似的问题,并且我最终编写了自己的锁来跟踪谁拥有什么读锁并将它们升级为写锁。 虽然这也是一种写时复制类型的读/写锁(允许一个写入者和读取者),所以它仍然有点不同。

What you're looking for is a lock upgrade, and is not possible (at least not atomically) using the standard java.concurrent ReentrantReadWriteLock. Your best shot is unlock/lock, and then check that noone made modifications inbetween.

What you're attempting to do, forcing all read locks out of the way is not a very good idea. Read locks are there for a reason, that you shouldn't write. :)

EDIT:
As Ran Biron pointed out, if your problem is starvation (read locks are being set and released all the time, never dropping to zero) you could try using fair queueing. But your question didn't sound like this was your problem?

EDIT 2:
I now see your problem, you've actually acquired multiple read-locks on the stack, and you'd like to convert them to a write-lock (upgrade). This is in fact impossible with the JDK-implementation, as it doesn't keep track of the owners of the read-lock. There could be others holding read-locks that you wouldn't see, and it has no idea how many of the read-locks belong to your thread, not to mention your current call-stack (i.e. your loop is killing all read locks, not just your own, so your write lock won't wait for any concurrent readers to finish, and you'll end up with a mess on your hands)

I've actually had a similar problem, and I ended up writing my own lock keeping track of who's got what read-locks and upgrading these to write-locks. Although this was also a Copy-on-Write kind of read/write lock (allowing one writer along the readers), so it was a little different still.

巴黎盛开的樱花 2024-07-19 01:45:31

至OP:
只要进入锁就解锁多少次,就这么简单:

boolean needWrite = false;
readLock.lock()
try{
  needWrite = checkState();
}finally{
  readLock().unlock()
}

//the state is free to change right here, but not likely
//see who has handled it under the write lock, if need be
if (needWrite){
  writeLock().lock();
  try{
    if (checkState()){//check again under the exclusive write lock
   //modify state
    }
  }finally{
    writeLock.unlock()
  }
}

在写锁中,就像任何自尊并发程序检查所需的状态一样。

HoldCount 不应在调试/监视/快速失败检测之外使用。

to OP:
just unlock as many times as you have entered the lock, simple as that:

boolean needWrite = false;
readLock.lock()
try{
  needWrite = checkState();
}finally{
  readLock().unlock()
}

//the state is free to change right here, but not likely
//see who has handled it under the write lock, if need be
if (needWrite){
  writeLock().lock();
  try{
    if (checkState()){//check again under the exclusive write lock
   //modify state
    }
  }finally{
    writeLock.unlock()
  }
}

in the write lock as any self-respect concurrent program check the state needed.

HoldCount shouldn't be used beyond debug/monitor/fast-fail detect.

∞琼窗梦回ˉ 2024-07-19 01:45:31

我想 ReentrantLock 是由树的递归遍历驱动的:

public void doSomething(Node node) {
  // Acquire reentrant lock
  ... // Do something, possibly acquire write lock
  for (Node child : node.childs) {
    doSomething(child);
  }
  // Release reentrant lock
}

您不能重构代码以将锁处理移到递归之外吗?

public void doSomething(Node node) {
  // Acquire NON-reentrant read lock
  recurseDoSomething(node);
  // Release NON-reentrant read lock
}

private void recurseDoSomething(Node node) {
  ... // Do something, possibly acquire write lock
  for (Node child : node.childs) {
    recurseDoSomething(child);
  }
}

I suppose the ReentrantLock is motivated by a recursive traversal of the tree:

public void doSomething(Node node) {
  // Acquire reentrant lock
  ... // Do something, possibly acquire write lock
  for (Node child : node.childs) {
    doSomething(child);
  }
  // Release reentrant lock
}

Can't you refactor your code to move the lock handling outside of the recursion ?

public void doSomething(Node node) {
  // Acquire NON-reentrant read lock
  recurseDoSomething(node);
  // Release NON-reentrant read lock
}

private void recurseDoSomething(Node node) {
  ... // Do something, possibly acquire write lock
  for (Node child : node.childs) {
    recurseDoSomething(child);
  }
}
画中仙 2024-07-19 01:45:31

那么,我们是否期望 java 仅当该线程尚未对 readHoldCount 做出贡献时才增加读取信号量计数? 这意味着与仅维护int类型的ThreadLocal readholdCount不同,它应该维护Integer类型的ThreadLocal Set(维护当前线程的hasCode)。 如果这没问题,我建议(至少现在)不要在同一个类中调用多个读取调用,而是使用一个标志来检查当前对象是否已获得读取锁定。

private volatile boolean alreadyLockedForReading = false;

public void lockForReading(Lock readLock){
   if(!alreadyLockedForReading){
      lock.getReadLock().lock();
   }
}

So, Are we expecting java to increment read semaphore count only if this thread has not yet contributed to the readHoldCount? Which means unlike just maintaining a ThreadLocal readholdCount of type int, It should maintain ThreadLocal Set of type Integer (maintaining the hasCode of current thread). If this is fine, I would suggest (at-least for now) not to call multiple read calls within the same class, but instead use a flag to check, whether read lock is already obtained by current object or not.

private volatile boolean alreadyLockedForReading = false;

public void lockForReading(Lock readLock){
   if(!alreadyLockedForReading){
      lock.getReadLock().lock();
   }
}
九厘米的零° 2024-07-19 01:45:31

像这样的事情怎么样?

class CachedData
{
    Object data;
    volatile boolean cacheValid;

    private class MyRWLock
    {
        private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
        public synchronized void getReadLock()         { rwl.readLock().lock(); }
        public synchronized void upgradeToWriteLock()  { rwl.readLock().unlock();  rwl.writeLock().lock(); }
        public synchronized void downgradeToReadLock() { rwl.writeLock().unlock(); rwl.readLock().lock();  }
        public synchronized void dropReadLock()        { rwl.readLock().unlock(); }
    }
    private MyRWLock myRWLock = new MyRWLock();

    void processCachedData()
    {
        myRWLock.getReadLock();
        try
        {
            if (!cacheValid)
            {
                myRWLock.upgradeToWriteLock();
                try
                {
                    // Recheck state because another thread might have acquired write lock and changed state before we did.
                    if (!cacheValid)
                    {
                        data = ...
                        cacheValid = true;
                    }
                }
                finally
                {
                    myRWLock.downgradeToReadLock();
                }
            }
            use(data);
        }
        finally
        {
            myRWLock.dropReadLock();
        }
    }
}

What about this something like this?

class CachedData
{
    Object data;
    volatile boolean cacheValid;

    private class MyRWLock
    {
        private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
        public synchronized void getReadLock()         { rwl.readLock().lock(); }
        public synchronized void upgradeToWriteLock()  { rwl.readLock().unlock();  rwl.writeLock().lock(); }
        public synchronized void downgradeToReadLock() { rwl.writeLock().unlock(); rwl.readLock().lock();  }
        public synchronized void dropReadLock()        { rwl.readLock().unlock(); }
    }
    private MyRWLock myRWLock = new MyRWLock();

    void processCachedData()
    {
        myRWLock.getReadLock();
        try
        {
            if (!cacheValid)
            {
                myRWLock.upgradeToWriteLock();
                try
                {
                    // Recheck state because another thread might have acquired write lock and changed state before we did.
                    if (!cacheValid)
                    {
                        data = ...
                        cacheValid = true;
                    }
                }
                finally
                {
                    myRWLock.downgradeToReadLock();
                }
            }
            use(data);
        }
        finally
        {
            myRWLock.dropReadLock();
        }
    }
}
℡寂寞咖啡 2024-07-19 01:45:31

可重入ReadWriteLock。 它清楚地表明,读取线程在尝试获取写锁时永远不会成功。 您试图实现的目标根本不被支持。 您必须在获取写锁之前释放读锁。 降级仍有可能。

可重入

此锁允许读取者和写入者重新获取读取或写入权限
采用 {@link ReentrantLock} 样式的锁。 不可重入读者
直到写入线程持有的所有写锁都被允许
已被释放。

此外,写入者可以获取读锁,但反之则不然。
在其他应用程序中,当写锁时重入会很有用
在调用或回调执行读取的方法期间保持
读锁。 如果读者尝试获取写锁,它永远不会
成功。

上述来源的示例用法:

 class CachedData {
   Object data;
   volatile boolean cacheValid;
   ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

   void processCachedData() {
     rwl.readLock().lock();
     if (!cacheValid) {
        // Must release read lock before acquiring write lock
        rwl.readLock().unlock();
        rwl.writeLock().lock();
        // Recheck state because another thread might have acquired
        //   write lock and changed state before we did.
        if (!cacheValid) {
          data = ...
          cacheValid = true;
        }
        // Downgrade by acquiring read lock before releasing write lock
        rwl.readLock().lock();
        rwl.writeLock().unlock(); // Unlock write, still hold read
     }

     use(data);
     rwl.readLock().unlock();
   }
 }

Found in the documentation for ReentrantReadWriteLock. It clearly says, that reader threads will never succeed when trying to acquire a write lock. What you try to achieve is simply not supported. You must release the read lock before acquisition of the write lock. A downgrade is still possible.

Reentrancy

This lock allows both readers and writers to reacquire read or write
locks in the style of a {@link ReentrantLock}. Non-reentrant readers
are not allowed until all write locks held by the writing thread have
been released.

Additionally, a writer can acquire the read lock, but not vice-versa.
Among other applications, reentrancy can be useful when write locks
are held during calls or callbacks to methods that perform reads under
read locks. If a reader tries to acquire the write lock it will never
succeed.

Sample usage from the above source:

 class CachedData {
   Object data;
   volatile boolean cacheValid;
   ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

   void processCachedData() {
     rwl.readLock().lock();
     if (!cacheValid) {
        // Must release read lock before acquiring write lock
        rwl.readLock().unlock();
        rwl.writeLock().lock();
        // Recheck state because another thread might have acquired
        //   write lock and changed state before we did.
        if (!cacheValid) {
          data = ...
          cacheValid = true;
        }
        // Downgrade by acquiring read lock before releasing write lock
        rwl.readLock().lock();
        rwl.writeLock().unlock(); // Unlock write, still hold read
     }

     use(data);
     rwl.readLock().unlock();
   }
 }
墨洒年华 2024-07-19 01:45:31

在 ReentrantReadWriteLock 上使用“公平”标志。 “公平”意味着锁定请求按照先到先得的方式提供服务。 您可能会遇到性能下降的情况,因为当您发出“写入”请求时,所有后续“读取”请求都将被锁定,即使它们可以在预先存在的读取锁仍处于锁定状态时得到服务。

Use the "fair" flag on the ReentrantReadWriteLock. "fair" means that lock requests are served on first come, first served. You could experience performance depredation since when you'll issue a "write" request, all of the subsequent "read" requests will be locked, even if they could have been served while the pre-existing read locks are still locked.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文