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
Related
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.
This question already has answers here:
What is the Re-entrant lock and concept in general?
(4 answers)
Closed 4 years ago.
So , a reentrant lock increments the count by one if the current thread acquires the lock again. what i am unable to understand is why and how that helps or benefits us?
The reason, that a reentrant lock is doing this, is to not lock the same Thread again, that has already acquired this lock.
For example: Let's say you have Thread A that is acquiring your Reentrant Lock A. Than Thread B tries to acquire the Lock A, which will result in Thread B getting blocked (more about the Thread states can be found here). Now, Thread A is trying to (again) acquire the Lock A.
Because the Reentrant lock is now highering its count, Thread A is not getting blocked. Thread A still has access over the Lock and can continue (the locks stores the informations about the depth). If he (sooner or later) releases the Lock, the count will be lowered again, to check if Thread A still need the Lock or not. If the count gets to 0, meaning Thread A has released for every time he has acquired, Thread B will get the access over the Lock.
Without reentrancy, you now would have a deadlock. Why? Because Thread A has the lock and would wait to get it again.
Concurrency can be realy complicated, reentrancy helps (just a bit) reducing this complexity.
This helps for the unusual situation when you want to call another method that also requires a lock.
ReentrantLock lock = new ReentrantLock();
public void doSomething() {
lock.lock();
try {
// Something.
} finally {
lock.unlock();
}
}
public void somethingElse () {
lock.lock();
try {
// Something else.
// We can now call another locking method without risking my lock being released.
doSomething();
} finally {
lock.unlock();
}
}
Here public can call doSomething and it will acquire the lock, do it's thing and then release the lock when unlock is called.
However, when somethingElse is called and it calls doSomething it just increases the lock count. When doSomething unlocks it does not release the lock, it merely counts the lock-count down, leaving the final unlock in somethingElse to release the lock.
The point of incrementing a count for the lock is to keep track of how many times the thread acquired the lock, so that the lock won't actually be released until the thread indicates readiness to release the lock the same number of times.
The assumption is that commands for locking will be matched with commands for releasing the lock.
Say my code enters Code Section A, which requires the lock.
Then without exiting Code Section A, it enters Code Section B which also requires the same lock. As you note we have the lock, so we needn't block.
But we'll leave Section B, and Section B is written to release the lock when we exit it. (Otherwise code that reaches Section B without already having the lock would never release the lock.)
We're still in Section A, though, so we don't want to really give up the lock yet, or another thread could take it while we're in Section A.
So when we entered Section B we incremented the lock count, which means when we exit Section B we can reduce the count by one and, seeing that it's not back to 0, not release it.
Then when Section A again releases the lock, the count falls back to 0 and that is when we really let the lock go.
Hi my question is how synchronization works?
In simple words we know that if a thread entered in a synchronization block by acquiring lock on any reference, than no other thread acquire that lock until first one exit from synchronized block.
But my question is if the thread acquired a lock on a reference and execute methodA() in that method there is a synchronized block, than can other thread acquire a lock on same reference and execute methodB(), there is also a synchronized block in it?
Synchronization is for mutual exclusion
Whenever you synchronize on an object, a lock is obtained on the monitor of that object.
Image Source: Thread synchronization
As the image shows as whenever a thread acquires a lock on monitor then it becomes the owner thread and no other thread can obtain lock on same monitor unless the owner thread enters wait state or releases the lock.
That said another point to keep in mind is that locks that are used in synchronized blocks are Reentrant, which means that if Thread 1 is the owner of the lock and same thread again tries to gain lock of which it is owner then Java will allow that.
Ok, than i have a issue. synchronized(b){ try{ b.wait();
}catch(InterruptedException e){} now as i acquired a lock on b object,
it means no other thread can acquire a lock on b object.
On calling wait(), the owner thread releases the lock and goes in Wait Set as shown in diagram. After that someone else from Entry set can get the lock.
I believe that the second lock will have to wait till the first lock has been released.
Have a look at the link Java synchronized references for more information.
Synchronization is very simple
One Thread locks any number of objects.
Only one thread can lock a given object at a given time.
When attempting to lock an object that is already locked by another thread, a thread has to wait until the object is released.
A Thread in wait state releases the locks it holds until it exists the wait state (at which time it attempts to reacquire the locks previously held).
In java, a lock is acquired using a synchronized block.
To answer your question: 2 threads can never acquire a lock on the same object at the same time.
When applying a reentrantReadWriteLock, and it is locked, what happens if another thread accesses the Lock while it is already performing another block? (Thus, before it reaches the .unlock)
Is the method canceled out? Or perhaps it's stalled? :O
Since you said ReentrantReadWriteLock, the behavior depends on whether you're talking about taking the read lock or the write lock associated with the ReadWriteLock.
If you're trying to acquire the write lock, you will be blocked until all holders release the lock (whether it is the read lock or the write lock)
If you're trying to acquire the read lock and there is no holder of the write lock, you will always be able to acquire it even if there are other read lock holders
If you're trying to acquire the read lock and there is a holder of the write lock, you will be blocked until the write lock holder releases the write lock
The read lock can be held concurrently by multiple threads as long as there is no writer.
The thread will block until the lock is available. (docs)
If you only want to acquire the lock if its available, you can use tryLock()
The thread will block. If more than one thread tries to acquire this lock, all of them will be blocked. When the lock is released, exactly one thread from the waiting pool will acquire the lock and the rest will still wait. See the difference between fair and unfair locks.
If you don't want to block you can use Lock.tryLock() (which tries without waiting) or tryLock(long time, TimeUnit unit) which will wait only as long as you specify.
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!