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 wit
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);
}
}
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();
}
}
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.
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.
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");
}
}
read to write test
<blocks indefinitely>
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();
}
}
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.
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.