Java ReentrantReadWriteLocks - how to safely get a write lock?

I am currently using ReentrantReadWriteLock in my code to synchronize access to the tree structure. This structure is large and read by many threads at the same time as random changes in its small parts - so it seems to be well suited to the read-write idiom. I understand that with this particular class it is not possible to lock a read lock to lock a write, as in Javadocs it is necessary to release a read lock before receiving a write lock. I have used this template successfully in non-reentrant contexts before.

However, I found that I cannot reliably get a write lock without locking forever. Since the read lock is reentrant and I actually use it as such, simple code

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

may be blocked if I purchased readlock again. Each unlock call simply reduces the number of holds, and the lock is actually released only when the number of samples reaches zero.

EDIT to clarify this, since I don’t think I explained it too well initially - I know that in this class there is no built-in escalation of the lock, and that I just need to release the read lock and get a write lock. My problem is that no matter what other threads do, a call to getReadLock().unlock() may not actually release this thread when it locks, if it acquired it again, in which case a call to getWriteLock().lock() will be blocked forever, since this thread still holds a read lock and thereby blocks itself.

For example, this piece of code will never reach the println statement, even if you run singlethreaded without any 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? In particular, when a stream containing a read lock (possibly repeatedly) discovers that it needs to perform some writing, and thus wants to “pause” its own read lock in order to obtain a write lock (lock, as required for other threads to let go of your tricks in reading lock), and then "raise" its commit to lock reading in the same state after that?

Since this implementation of ReadWriteLock was specifically designed to be reentrant, is there really any reasonable way to increase read locks to write locks, when locks can be re-acquired? This is the critical part, which means that the naive approach does not work.

+60
java concurrency reentrantreadwritelock
Jan 21 '09 at 10:42
source share
12 answers

I have made little progress on this. ReentrantReadWriteLock declaring the lock variable as ReentrantReadWriteLock instead of just ReadWriteLock (less than ideal, but probably necessary evil in this case), I can call getReadHoldCount() . This allows me to get the number of holds for the current thread, and thus I can unlock readlock multiple times (and get it again with the same number). Thus, 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(); } 

And yet, this will be the best I can do? It is not very elegant, and I still hope that there is a way to handle this less "manually."

+27
Jan 21 '09 at 10:58
source share

What you want to do should be possible. The problem is that Java does not provide an implementation that can update read locks to write locks. In particular, javadoc's ReentrantReadWriteLock says that it does not allow updating read locks to write locks.

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

Why do we need a read upgrade for writing locks

Updating from read to write is blocked (despite claims to the contrary in other answers). A deadlock may occur, and therefore part of the implementation is code for recognizing deadlocks and breaking them by eliminating an exception to the thread to break the deadlock. This means that as part of your transaction, you must throw a DeadlockException, for example, by repeating work. Typical pattern:

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

Without this feature, the only way to implement a serializable transaction that sequentially reads a bunch of data and then write something based on what you read is to anticipate that the message will be needed before you start, and therefore get WRITE locks on all data which are read before writing what needs to be written. This is KLUDGE, which uses Oracle (SELECT FOR UPDATE ...). Moreover, it actually reduces concurrency, because no one can read or write any data during a transaction!

In particular, releasing a read lock before receiving a write lock will result in inconsistent results. Consider:

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

You need to know if someMethod () creates or any method that it calls creates a re-entry lock on y! Suppose you know that. Then, if you first release the read lock:

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

another thread can change y after you release its read lock, and before you get a write lock on it. Therefore, the value of y will not be equal to x.

Test code: update read lock write lock:

 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> 
+23
Jan 29 '11 at 2:02
source share

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

As others have noted, a classic read-write lock (for example, the JDK ReentrantReadWriteLock ) does not essentially support updating a read-write lock, because it can lead to a deadlock.

If you need to securely acquire a write lock without first releasing a read lock, there is, however, a more efficient alternative: instead, pay attention to the read-write lock of the update .

I wrote ReentrantReadWrite_Update_Lock and released it as an open source under the Apache 2.0 license here . I also posted detailed information on the JSR166 concurrency -interest mailing list approach, and this approach survived this list some time ago.

This approach is quite simple, and as I mentioned in concurrency -interest, the idea is not entirely new, as it was discussed on the Linux kernel mailing list at least as far back as 2000. Also .Net platform ReaderWriterLockSlim also supports blocking update. So effectively, this concept has simply not been implemented in Java (AFAICT) so far.

The idea is to provide an update lock in addition to a read lock and write lock. An update lock is an intermediate type of lock between a read lock and a write lock. Like a write lock, only one thread can receive an update lock at a time. But like a read lock, it allows you to read access to the thread that holds it, and at the same time as other threads that contain regular read locks. The key feature is that the update lock can be updated from its read-only status until the write lock, and this is not amenable to lock because only one thread can hold the update lock and be able to update at the same time.

This supports update locks and is also more efficient than regular read-write locks in applications with read-write access patterns, since it blocks the reading of threads for shorter periods of time.

An example of use is provided on the site . The library has 100% testing coverage and is located in the center of Maven.

+15
Sep 13 '13 at 11:31 on
source share

What you are trying to do is simply not possible.

You cannot have a read / write lock, which you can update from read to write without problems. Example:

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

Now suppose that two threads go into this function. (And you assume concurrency, right? Otherwise, you still don't need locks ....)

Suppose that both threads start at the same time and run equally fast. This would mean that both would acquire a reading lock, which is perfectly legal. However, then both will eventually try to obtain a write lock that they NEVER will receive: the corresponding other threads contain a read lock!

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

+8
Apr 04 '10 at 23:45
source share

What you are looking for is a lock update and is impossible (at least not in atomic mode) using the standard java.concurrent ReentrantReadWriteLock. Your best shot is unlock / lock, and then check that no one has made changes between them.

What you are trying to do by making all read locks go down is not a good idea. Read the locks for some reason that you shouldn't write. :)

EDIT:
As Ran Biron noted, if your problem is hunger (readable locks are set and released all the time, never falling to zero), you can try to use fair order. But your question didn’t sound like it was your problem?

EDIT 2:
Now I see your problem, you actually acquired several stacks for reading on the stack, and you want to convert them to write lock (update). This is actually not possible with the JDK implementation, as it does not track owners of read locks. There may be others that have read locks that you don’t see, and they don’t know how many of the read-lock blocks belong to your thread, not to mention your current call stack (i.e., your loop kills all read locks, and not just your own, so your write lock will not wait for any of your closest readers to finish and you will get a mess on your hands)

I actually had a similar problem, and I ended up writing my own journal, keeping track of who got what it reads, blocks and updates them to write locks. Although it was also a Copy-on-Write type recording method (allowing one writer to write along readers), so it was a bit different.

+4
Jan 21 '09 at 11:01
source share

In Java 8, there is now java.util.concurrent.locks.StampedLock with the tryConvertToWriteLock(long) API

Additional information at http://www.javaspecialists.eu/archive/Issue215.html

+3
Feb 24 '15 at 6:05
source share

What about this one?

 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(); } } } 
+2
Jul 11 '14 at 7:55
source share

I believe ReentrantLock motivated by a recursive tree 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 } 

Can you reorganize your code to move lock processing outside of 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); } } 
+1
Jan 21 '09 at 12:15
source share

in OP: just unlock as many times as you entered the lock, just:

 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 record lock, as any parallel program for self-esteem checks the necessary state.

HoldCount should not be used outside of debugging / monitoring / quick break detection.

+1
Mar 21 '10 at 13:30
source share

So, do we expect java to increase the number of semaphore reads only if this thread has not yet contributed to readHoldCount? This means that unlike just maintaining an int ThreadLocal readholdCount of type int, it must support an Integer of type ThreadLocal Set (supporting the hasCode of the current thread). If this is normal, I would suggest (at least for now) not to invoke multiple read calls in the same class, but instead use a flag to verify that the read lock is already received by the current object or not.

 private volatile boolean alreadyLockedForReading = false; public void lockForReading(Lock readLock){ if(!alreadyLockedForReading){ lock.getReadLock().lock(); } } 
+1
Dec 02 '11 at 20:52
source share

Found in the documentation for ReentrantReadWriteLock . It clearly states that reader threads will never succeed when trying to obtain a write lock. What you are trying to achieve is simply not supported. You must release the read lock before receiving the write lock. Lowering bids is still possible.

Reentrant

This lock allows readers and writers to re-read or write locks in the style of {@link ReentrantLock}. Unintentional readers are not allowed until all write locks held by the recording stream have been released.

In addition, a writer can get a read lock, but not vice versa. Among other applications, re-allocation may be useful when write locks during calls or callbacks to methods that perform reads in accordance with read locks. If the reader tries to get a write lock, he will never succeed.

Using an example from the source above:

  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(); } } 
0
Jul 02 '14 at 15:07
source share

Use the "fair" flag on the ReentrantReadWriteLock. “fair” means that lock requests are submitted the first time, first submitted. You may experience performance degradation because when you select the write request, all subsequent read requests will be blocked, even if they could be sent while existing read locks are still locked.

-one
Jan 21 '09 at 10:59
source share



All Articles