Lets understand above point with an example, When thread 1 tries to call any synchronized method or synchronized block, It has to wait until some other thread say thread 2 release the lock on the same monitor. What if the thread 2 doesn't release the monitor due to some reason, How much time thread 1 has to wait, there is no control to the programmer till when Thread 1 will be waiting.
With Synchronized keyword, lock need to be acquired and released at complete method level or at block level. Lets say when thread t1 tries to acquire multiple locks by calling multiple synchronized method, in that case multiple locks are acquired by t1 and they must all be released in the opposite order.
In ReentrantLock, locks can be acquired and released in different scopes, and allowing multiple locks to be acquired and released in any order.
Example:
ReentrantLock reentrantLock;
public void getA() {
reentrantLock.lock();
}
public void getB() {
reentrantLock.unlock();
}
More than one waiting condition
When lock is acquired using intrinsic lock by calling synchronized method/block, this threads then communicate using wait(), notify() and notifyAll() methods of Object class.
Condition allows inter thread communication when lock is acquired using extrinsic way by Lock interface. Condition defines methods such as await(), signal() and signalAll() for waiting and notifying.
Using synchronized block/method for a common monitor, there is no way to distinguish for what reason each thread is waiting, thread t1, t2, t3 might be blocked say for putting the data in the queue, other threads say t5, t6, t7, t8 might be waiting for reading data from the queue and they all are waiting on common monitor "queue".
Lets consider producer consumer situation, say we have a queue of size one and is full and t1, t2, t3 is blocked for putting the data in the queue, so they are in waiting state.
Now, t5, t6, t7, t8 tries to read data from the queue, lets say t5 would be reading the data in the queue, meanwhile t6, t7, t8 would be in waiting state.
After t5 read the data from the queue, it calls notifyAll, this call is to notify producers(t1,t2,t3) to put the data in the queue as there is a space now,
there are total 6 threads waiting for monitor "queue"
putting data in queue = t1, t2, t3,
reading data from queue = t4, t6, t7
currently monitor is held by executing thread = t5
when t5 calls notifyAll, there is no guarantee who is going to be wake up, might be thread t7 wake up and it has to go back to waiting state again as nothing is there to read, next time might be t4 gets a chance and again no use of t4 wakeup and it will go back to waiting state.
When someone from t1, t2 or t3 wakes up then only things would proceed.
If there is a way for t5 thread to notifyAll only to threads that want to put data to queue t1, t2 and t3 then it would be helpful. Using Condition that is possible.
With intrinsic lock using synchronized method/block there is no way to group the waiting threads waiting on a common monitor. with Condition, we can create multiple wait sets.
When you use Condition: await()/signal() you can distinguish which object or group of objects/threads get a specific signal.
Using Condition, we now have way to create more than one condition variable per monitor.
Monitors that use the synchronized keyword can only have one. This means Reentrant locks(implementation of Lock interface) support more than one wait()/notify() queue.
private final Lock lock = new ReentrantLock();
private final Condition queueEmpty = lock.newCondition();
private final Condition queueFull = lock.newCondition();
public void putData(int data) {
lock.lock();
try {
while (queue is empty) {
queueEmpty.await();
}
this.data = data;
queueFull.signalAll();
} finally {
lock.unlock();
}
}
public void getData() {
lock.lock();
try {
while (queue is full) {
queueFull.await();
}
queueEmpty.signalAll();
} finally {
lock.unlock();
}
}
Now with queueFull.signalAll(), only threads those are waiting for this condition on same monitor "lock" will be awaked and rest will still be waiting.
Condition interface also comes with useful method that is:
boolean awaitUntil(Date deadline): Causes the current thread to wait until it is signaled or interrupted, or the specified deadline elapses.
Note: there is similar method wait(long timeInMilliseconds), but when there is System date change, above method will have impact while wait will keep waiting for provided timeInMilliseconds. So decide which is better in your situation.
Question 2. Does synchronized method and block are reentrant?
Yes. synchronized method, synchronized block and Reentrant lock are all reentrant in nature.
What is the meaning of Reentrant?
A reentrant lock is one where a process can claim the lock multiple times without blocking on itself.
In simple terms, ability to call the same synchronized method again and again without getting blocked is called reentrant.
Lets understand with example,
synchronized void getA () {
getB();
}
synchronized void getB () {
getA();
}
What will happen if say Thread 1 calls obj.getA(), thread 1 will acquire a lock on obj and call method getA(). inside which it calls getB()(which is obj.getB()), thread 1 already hold the lock on obj so it will call getB(),
getB() call getA()(which is obj.getA()), thread 1 already hold a lock on obj so it is allowed to call the method getA() again. this is called Reentrant. same lock is claimed multiple times that is each time getA is called.
Question 3. Show simple example on how to write lock and unlock method of Reentrant Lock?
public void getA() {
reentrantLock.lock();
try{
//...
} catch(Exception e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
Question 4. Why ReentrantLock is called ReentrantLock?
ReentrantLock keep track of lock acquisition count associated with the lock.
when a call reentrantLock.lock() is made to acquire a lock and if the lock is obtained then the acquisition count variable is incremented to 1, stating that lock has been acquired one time till now.
Similarly, when a call reentrantLock.unlock() is made acquisition count variable is decremented by 1.
When the count reaches 0 then only other thread will be allowed to take the lock.
When a thread t1 acquires a reentrant lock inside method say getA() and make a call to another method say getB() from inside getA() which is also guarded by reentrant lock, in this case thread t1 will acquire a lock twice one for getA() method and one for getB() method. In this case, if a thread t1 that is already holding a lock is now acquiring it again inside getB(), the acquisition count is incremented to 2 and now the lock needs to be released twice to fully release the lock.
Let's see sample program,
package com.javabypatel.concurrency;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
public static void main(String[] args) {
ReentrantLock reentrantLock = new ReentrantLock();
Thread t1 = new Thread(new Printer("Thread1", reentrantLock));
Thread t2 = new Thread(new Printer("Thread2", reentrantLock));
t1.start();
t2.start();
}
}
class Printer implements Runnable {
private String threadName;
private ReentrantLock reentrantLock;
Printer(String threadName, ReentrantLock reentrantLock) {
this.threadName = threadName;
this.reentrantLock = reentrantLock;
}
@Override
public void run() {
System.out.println("Thread " + threadName + " is waiting to get lock");
reentrantLock.lock();
try {
System.out.println("Thread " + threadName + " acquired lock");
getA();
} finally {
reentrantLock.unlock();
System.out.println("Thread " + threadName + " released the lock and the lock held count is :"+reentrantLock.getHoldCount());
}
}
public void getA() {
System.out.println("getA :: Thread " + threadName + " is waiting to get lock");
try {
reentrantLock.lock();
System.out.println("getA :: Thread " + threadName + " acquired lock");
System.out.println("getA :: Lock count held by thread " + threadName + " : " + reentrantLock.getHoldCount());
} finally {
reentrantLock.unlock();
System.out.println("getA :: Thread " + threadName + " released the lock and the lock held count is :"+reentrantLock.getHoldCount());
}
}
}
Output:
Thread Thread1 is waiting to get lock
Thread Thread1 acquired lock
getA :: Thread Thread1 is waiting to get lock
getA :: Thread Thread1 acquired lock
getA :: Lock count held by thread Thread1 : 2
getA :: Thread Thread1 released the lock and the lock held count is :1
Thread Thread1 released the lock and the lock held count is :0
Thread Thread2 is waiting to get lock
Thread Thread2 acquired lock
getA :: Thread Thread2 is waiting to get lock
getA :: Thread Thread2 acquired lock
getA :: Lock count held by thread Thread2 : 2
getA :: Thread Thread2 released the lock and the lock held count is :1
Thread Thread2 released the lock and the lock held count is :0
You can see lock held count should go back to 0 for another thread to acquire a lock.
You may also like to see
Enjoy !!!!
If you find any issue in post or face any error while implementing, Please comment.