In this article, we will understand how to use reentrant lock in Java. Let’s get started.
Table of contents
- Reentrant lock
- Deadlock
- Some methods of Reentrant lock
- When to use Reentrant lock
- Benefits and drawbacks
- Different between Reentrant lock and synchronized keyword
- Wrapping up
Reentrant lock
Assuming that we have two instances of Person class - Mary and John, and we have a bunch of synchronized method in those instances. Now it turns out that the first synchronized method from the Mary instance, calls another synchronized method of the John instance, that happens to be synchronized with the same lock object.
Here our methods are guarded with red fences, and to enter this method, we need the red key. So, a thread that is running this method from the Mary object will take the key, and at some point will need to enter the synchronized method from the John instance. The red key needs to be available for this thread enter this method. It turns out that this key is not available. But the thread holding that key is precisely the thread that is asking for it. So this is an exception to the rule called reentrance. And this thread, since it is already holding the right key, will be allowed to run the other method.
This rule is quite natural to understand, but it still has to be stated.
When a thread holds a lock, it can enter a block synchronized on the lock it is holding.
Deadlock
We still have two Mary and John instances of Person class. And we have a synchronized method that is calling another synchronized method. Now we are not in the same case as the previous Reentrant lock section that is the first method is synchronized using a red key, and the method called by this method is synchronized using a green key.
And for some reasons, this green protected method calls another method, the third one, protected also by the red key. What is going to happen in this case?
The blue thread is going to take the red key and begin to run the first method. And at the same time, the purple thread is going to take the green key, and to run the other method.
At some point, the blue thread will need the green key to enter the green method, but the purple thread has it.
So this blue thread has to wait. And the purple thread will arrive at the point of code where it needs the red key to continue to run. And unfortunately the red key is available, because it is held by the blue thread. And this case is a deadlock, that is, the green key will never be released by the purple thread.
So because of that, the blue thread is blocked. And since it is holding the red key needed by the purple thread, the purple thread will never continue to advance, nor the blue thread.
–> A deadlock is a situation where a thread T1 holds a key needed by a thread T2, and T2 holds the key needed by T1.
So as long as no thread releases its key, the situation is blocked, and called a deadlock.
Fortunately, the JVM is able to detect deadlock situations, and can log information to help debug the application.
But there is not much we can do if a deadlock situation occurs, beside rebooting the JVM.
Some methods of Reentrant lock
-
lock()
methodpublic void lock()
- Acquires the lock if it is not held by another thread and returns immediately, setting the lock hold count to one.
- If the current thread already holds the lock then the hold count is incremented by one and the method returns immediately.
- If the lock is held by another thread then the current thread becomes disabled for thread scheduling purposes and lies dormant until the lock has been acquired, at which time the lock hold count is set to one.
-
unlock()
methodpublic void unlock()
- Attempts to release this lock.
- If the current thread is the holder of this lock then the hold count is decremented. If the hold count is now zero then the lock is released. If the current thread is not the holder of this lock then IllegalMonitorStateException is thrown.
-
tryLock()
methodpublic boolean tryLock()
-
Acquires the lock if it is not held by another thread and returns immediately with the value true, setting the lock hold count to one. Even when this lock has been set to use a fair ordering policy, a call to
tryLock()
will immediately acquire the lock if it is available, whether or not other threads are currently waiting for the lock. This “barging” behavior can be useful in certain circumstances, even though it breaks fairness. If you want to honor the fairness setting for this lock, then usetryLock(0, TimeUnit.SECONDS)
which is almost equivalent (it also detects interruption). -
If the current thread already holds this lock then the hold count is incremented by one and the method returns
true
. -
If the lock is held by another thread then this method will return immediately with the value
false
.
-
-
tryLock(long timeout, TimeUnit unit)
methodpublic boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException
- The thread waits for a certain time period as defined by arguments of the method to acquire the lock on the resource before exiting.
-
lockInterruptibly()
methodpublic void lockInterruptibly() throws InterruptedException
-
If the lock is held by another thread then the current thread becomes disabled for thread scheduling purposes and lies dormant until one of two things happens:
- The lock is acquired by the current thread; or
- Some other thread interrupts the current thread.
-
If the current thread:
- has its interrupted status set on entry to this method; or
- is interrupted while acquiring the lock,
then
InterruptedException
is thrown and the current thread’s interrupted status is cleared. -
It means that if the current thread is waiting for lock but some other thread requests the lock, then the current thread will be interrupted and return immediately without acquiring lock.
-
-
getHoldCount()
methodpublic int getHoldCount()
- Queries the number of holds on this lock by the current thread.
- A thread has a hold on a lock for each lock action that is not matched by an unlock action.
- The hold count information is typically only used for testing and debugging purposes.
-
isHeldByCurrentThread()
methodpublic boolean isHeldByCurrentThread()
- Queries if this lock is held by the current thread.
- This method is typically used for debugging and testing.
-
It can also be used to ensure that a reentrant lock is used in a non-reentrant manner, for example:
class X { ReentrantLock lock = new ReentrantLock(); // ... public void m() { assert !lock.isHeldByCurrentThread(); lock.lock(); try { // ... method body } finally { lock.unlock(); } } }
When to use Reentrant lock
-
use it when we actually need something it provides that
synchronized
doesn’t, like timed lock waits, interruptible lock waits, non-block-structured locks, multiple condition variables, or lock polling. -
ReentrantLock
also has scalability benefits, and we should use it if we actually have a situation that exhibits high contention, but remember that the vast majority of synchronized blocks hardly ever exhibit any contention, let alone high contention. We would advise developing with synchronization until synchronization has proven to be inadequate, rather than simply assuming the performance will be better if we useReentrantLock
.
Benefits and drawbacks
- Benefits
Reentrant Lock
provides explicit locking that is much more granular and powerful thansynchronized
keyword.
For example:
- The scope of lock can range from one method to another but scope of
synchronized
keyword can not go beyond one method.
-
Lock framework also supply
Condition variables
that are instances ofCondition
class, which provides inter thread communication methods similar towait()
,notify()
andnotifyAll()
such asawait()
,signal()
andsignalAll()
.So, if one thread is waiting on a condition by calling
condition.await()
, then once that condition changes, second thread can callcondition.signal()
orcondition.signalAll()
method to notify that its time to wake up, condition has been changed.
- Drawbacks
- Difficult to use, understand for the novice programmers.
Different between Reentrant lock and synchronized keyword
For example:
private ReentrantLock lock;
public void foo() {
...
lock.lock();
...
}
public void bar() {
...
lock.unlock();
...
}
Some interesting question for Reentrant lock
-
How does the
ReentrantLock
avoid deadlock?In general, the
ReentrantLock
does not.In the case of reentrant locking (i.e. in the case where a thread attempts to acquire
lock A
while holdinglock A
), theReentrantLock
implementation notices that the thread holding the lock is the thread acquiring the lock. A counter is incremented, so that the implementation knows that the lock must be released twice. -
How can we avoid deadlock in this situation using
ReentrantLock
?-
Ensure that all threads acquire the locks in the same order. (Deadlocks occur when threads attempt to acquire two or more threads in a different order.)
-
If
tryLock()
fails while holding a different lock, release the lock, wait a bit, and try again.
-
Wrapping up
-
One can forget to call the
unlock()
method in thefinally
block leading to bugs in the program. Ensure that the lock is released before the thread exits. -
The
fairness
parameter used to construct the lock object decreases the throughput of the program. -
There are two implementations of
Lock
interface -ReentrantLock
andReentrantReadWriteLock
. -
If a thread already holds the lock on a monitor object, it has access to all blocks synchronized on the same monitor object. This is called reentrant. The thread can reenter any block of code for which it already holds the lock.
-
ReentrantLock
is mutual exclusive lock, similar to implicit locking provided bysynchronized
keyword in Java, with extended feature likefairness
, which can be used to provide lock to longest waiting thread. -
ReentrantLock
is a concrete implementation ofLock
interface in concurrency package ofJava 1.5
. -
A
ReentrantLock
is owned by the thread last successfully locking, but not yet unlocking it. A thread invoking lock will return, successfully acquiring the lock, when the lock is not owned by another thread. The method will return immediately if the current thread already owns the lock. This can be checked using methodsisHeldByCurrentThread()
, andgetHoldCount()
. -
A Thread owns the
ReentrantLock
which is intended to perform the write operation on some particular data. -
It is recommended practice to always immediately follow a call to lock with a
try block
, most typically in a before/after construction such as:class X { private final ReentrantLock lock = new ReentrantLock(); // ... public void m() { lock.lock(); // block until condition holds try { // ... method body } finally { lock.unlock() } } }
This lock supports a maximum of
2147483647
recursive locks by the same thread. Attempts to exceed this limit result in Error throws from locking methods.
Refer:
Appying Concurrency and Multithreading to Common Java patterns in pluralsight.com
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantLock.html
https://www.ibm.com/developerworks/java/library/j-jtp10264/
https://winterbe.com/posts/2015/04/30/java8-concurrency-tutorial-synchronized-locks-examples/
https://stackoverflow.com/questions/11821801/why-use-a-reentrantlock-if-one-can-use-synchronizedthis