Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java ReentrantReadWriteLocks - how to safely acquire write lock?

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.

like image 969
Andrzej Doyle Avatar asked Jan 21 '09 10:01

Andrzej Doyle


People also ask

How do you implement a read/write lock in Java?

ReadWriteLock interface allows multiple threads to read at a time but only one thread can write at a time. Read Lock − If no thread has locked the ReadWriteLock for writing then multiple thread can access the read lock. Write Lock − If no thread is reading or writing, then one thread can access the write lock.

What is the difference between lock and ReentrantLock?

Lock is an interface. It defines a set of methods that all locks should have. ReentrantLock is a concrete class that implements the Lock interface. It implements all the methods defined in Lock , plus much more.

What is read/write lock What is reentrant lock?

The ReadWriteLock is a pair of associated locks, one for read-only operations and one for writing. Whereas, the ReentrantLock is a re-entrant mutual exclusion Lock with the same behavior as the implicit monitor lock accessed using synchronized methods and statements, but with some more extended capabilities.

What is stamped lock in Java?

The state of a StampedLock consists of a version and mode. Lock acquisition methods return a stamp that represents and controls access with respect to a lock state; "try" versions of these methods may instead return the special value zero to represent failure to acquire access.


2 Answers

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.

like image 62
npgall Avatar answered Sep 21 '22 19:09

npgall


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.

like image 43
Andrzej Doyle Avatar answered Sep 19 '22 19:09

Andrzej Doyle