Why does the following function deadlock? In other words, why does it prevent anyone from obtaining, not only the write lock, but the read lock? Can't read locks be shared?
void testReadWriteLock() throws InterruptedException {
final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
// Other thread acquires read lock and never releases it.
new Thread(() -> lock.readLock().lock()).start();
Thread.sleep(100);
// Other thread attempts (and fails) to acquire write lock.
new Thread(() -> lock.writeLock().lock()).start();
Thread.sleep(100);
lock.readLock().lock(); // This blocks forever
}
I discovered an interesting property of ReentrantReadWriteLock: any attempt to acquire the write lock (even blocking) blocks all subsequent attempts to acquire read locks (except re-entrant attempts). At least, that's the case in Oracle's 1.8 JVM. There is a certain logic in prioritizing writes: it ensures that data is up-to-date.
You can have multiple threads obtain the read lock, but when the write lock is obtained, everyone else is blocked out. Since you never unlock anything, the final lock call in the method will never obtain the lock.
Related
How does the java thread acquire a lock on a monitor used in synchronized block or monitor used in synchronized methods?
I read on multiple posts that in case of biased locking this information is stored in the object header using CAS operation and in case of contended situation wait set queue/ monitor queue is used but eventually lock marked in the object header only.
If this is the case then how is lock released? How object is marked free for acquiring a lock by another thread? are wait and notify methods used internally for this? If this is the case then why is making monitor null inside the synchronized block does not throw any exception.
The below example works perfectly fine, I was expecting NullPointerException assuming the end of the synchronized block will try to mark lock property to free the lock.
Example:
Object monitor = new Object();
synchronized (monitor){
System.out.println("before null");
monitor =null;
System.out.println("after null");
}
System.out.println("successfully Exited");
In case of biased locking: if the lock is biased towards a certain thread, no CAS is needed; just a volatile write. Biased lock information is kept in the mark word of the object header. Biased locking is going to be removed from JDK 15.
If a lock is contended, the object-monitor is used for synchronization. By default the object monitor is deflated, but if there is contention or you do a wait/notify, then the monitor gets inflated and is attached to the object.
On Linux blocking behavior is implemented using a wait-queue. So when a thread needs to wait for a lock, it is removed from the scheduler and added to the wait queue. When a lock unlocks, the thread on the wait queue is reinserted back into the scheduler.
The reason why the code doesn't throw an exception is that the monitor is read only once when the synchronized block is entered.
PS: It could be that your lock get completely removed due to lock elision. If the JIT can provide no other thread can acquire that lock, there is no point in synchronizing.
From the ReentrantReadWriteLock class javadoc:
void processCachedData() {
rwl.readLock().lock();
if (!cacheValid) {
// Must release read lock before acquiring write lock
5: rwl.readLock().unlock();
6: 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
14: rwl.readLock().lock();
15: rwl.writeLock().unlock(); // Unlock write, still hold read
}
use(data);
rwl.readLock().unlock();
}
Why must we release the read lock before acquiring the write lock as written in the comment? If the current thread holds the read lock, then it should be allowed to acquire the write lock when other threads are not reading anymore, regardless of whether the current thread also holds the read lock. This is the behavior I would expect.
I would expect the lock upgrade at lines 5 and 6 and the lock downgrade at lines 14 and 15 to be done internally in the ReentrantReadWriteLock class. Why is that not possible?
In other words, I would expect the code to work fine like this:
void processCachedData() {
rwl.readLock().lock();
if (!cacheValid) {
// The readlock is upgraded to writeLock when other threads
// release their readlocks.
rwl.writeLock().lock();
// no need to recheck: other threads can't have acquired
// the write lock since this thread still holds also the readLock!!!
if (!cacheValid) {
data = ...
cacheValid = true;
}
// Downgrade by acquiring read lock before releasing write lock
rwl.writeLock().unlock(); // Unlock write, still hold read
}
use(data);
rwl.readLock().unlock();
}
This looks like a much better and safer way to handle locking, doesn't it?
Can somebody explain the reason for this weird implementation? Thanks.
The problem with upgrading a read lock to a write lock is, if two threads try to do it at the same time, it can lead to deadlock. Consider the following sequence:
Thread A: acquires read lock
Thread B: acquires read lock (Note that both threads now share the read lock).
Thread A: tries to acquire write lock (blocks as thread B holds read lock)
Thread B: tries to acquire write lock (blocks as thread A holds read lock)
Now there is a deadlock. Neither thread A or B can proceed, and neither will ever release the read lock.
The Javadoc explicitly states upgrading a read lock to a write lock is not possible. The reason for that is because it can create a deadlock.
thread 1 acquire read lock
thread 2 acquire read lock
thread 1 ask to upgrade lock to write
thread 2 ask to upgrade lock to write
Both threads are now waiting on each other ... forever.
Reentrancy allows downgrading from the write lock to a read lock, by acquiring the write lock, then the read lock and then releasing the write lock. However, upgrading from a read lock to the write lock is not possible (which results in a deadlock).
Source: http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html
I have a reentrant lock which I am wrapping in a customized class for my own needs. However due to the nature of the application a thread holding the lock to the reentrant lock gets stuck (external failures) and fails to release the reentrant lock.
I am wondering if there is a method to explicitly unlock the reentrant lock? I know the API for Reentrant lock does not have such a method - however I was thinking of introducing a timer task which will unlock the Reentrant lock after a set period of time OR kill the thread which holds the reentrant lock.
Any other suggestions in trying to force unlock my reentrant lock? My solutions are pretty thus I ask.
Instead of unlocking externally, I would execute the blocking code in a separate thread and have it timeout
something like this
Future<MyTask>future = taskExecutor.submit(myTask)
try {
future.get(5,TimeUnit.Seconds);
...
}
catch (Exception e)
{
future.cancel(true); // attempt to interupt the thread
throw new Exception();
}
As per my comment, any lock should be wrapped around a try/finally block to ensure that the lock is released if something goes wrong
_lock.lock(); // will wait until this thread gets the lock
try
{
// critical section
}
finally
{
//releasing the lock so that other threads can get notifies
_lock.unlock();
}
This is demonstrated in the Lock Objects trail
I'm not sure if I'm interpreting the javadoc right. When using a ReentrantLock after calling the lock method and successfully gaining a lock, can you just access any object without any synchronized blocks and the happend-before relationship is magically enforced?
I don't see any connection between the ReentrantLock and the objects I'm working on, that's why it is hard to believe I can work on them safely. But this is the case, or am I reading the javadoc wrong?
If thread A has modified some object inside a code block CB1 guarded by the lock and then releases the lock, and thread B enters in a code block guarded by the same lock, then thread B will see the modifications done by thread A in the code block CB1.
If two threads read and write the same shared state, then every read and write to this state should be guarded by the same lock.
It's ... a (mutex) lock:
void myMethod()
{
myLock.lock(); // block until condition holds
try
{
// Do stuff that only one thread at a time should do
}
finally
{
myLock.unlock()
}
}
Only one thread can hold the lock at a time, so anything between the lock() and unlock() calls is guaranteed to only be executed by one thread at a time.
The relevant Oracle tutorial can be found here.
There's no magic in it. You're safe if, and only if, all threads accessing an object use the same lock - be it a ReentrantLock or any other mutex, such as a synchronized block.
The existence ReentrantLock is justified by that it provides more flexibility than synchronized: you can, for example, just try to acquire the lock - not possible with synchronized.
What is Upgrade/Downgrade of ReentrantReadWriteLock?
I see javadoc about Upgrade/Downgrade:
"Lock downgrading :
Reentrancy also allows downgrading from the write lock to a read lock, by acquiring the write lock, then the read lock and then releasing the write lock. However, upgrading from a read lock to the write lock is not possible."
And a sample provided:
class CachedData {
Object data;
volatile boolean cacheValid;
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
rwl.readLock().lock();
if (!cacheValid) {
// upgrade lock manually
rwl.readLock().unlock(); // must unlock first to obtain writelock
rwl.writeLock().lock();
if (!cacheValid) { // recheck
data = ...
cacheValid = true;
}
// downgrade lock
rwl.readLock().lock(); // reacquire read without giving up write lock
rwl.writeLock().unlock(); // unlock write, still hold read
}
use(data);
rwl.readLock().unlock();
}
}
I know it talks about the relation between readLock and writeLock, but I couldn't get the clear concept from the doc. Could you give me a little more explanation? Thanks!
I believe that in this context the idea of "upgrading" and "downgrading" is based on the idea that the reader lock is, in a sense, a "weaker" lock than the writer lock. When the write lock is acquired, no other threads can acquire the lock in any form, whereas with a reader lock any other thread can acquire the read lock if it wants to.
Here, "downgrading" the lock means that if you hold the write lock, you can switch down to holding just the read lock by acquiring the read lock, then releasing the write lock. This means that you can have a thread that starts off doing something critically important (something that would prevent other threads from reading), does its work, and then switches to the lower-priority lock (the read lock) without ever being without the lock. This allows you to hold the lock continuously without getting preempted.
However, the other way doesn't work - once you're holding the read lock, you can't "upgrade" to holding the more important write lock by trying to acquire the write lock. If you tried to do this, the thread would just block until it was interrupted.
Hope this helps!