ReentrantLock interview questions in Java
Reentrant lock is more feature rich than using synchronized method/block.
This post assume you have basic idea about Threads and synchronization in Java, If not I recommend going through below Multithreading articles first,
When using synchronized method or a synchronized block, process of lock acquisition and release is actually handled internally but with Reentrant Lock, programmer has full control over thread locks and thread locking management is on developer by explicitly calling lock and unlock method, so there is both advantages and disadvantages of this Reentrantlock.
Reentrantlock interview questions in java |
Question 1: What is the difference between ReentrantLock and Synchronized keyword in Java?
Time to wait for getting a Lock:
When a thread invoke synchronized method or a block, It has to first acquire a lock and then only it can proceed, there is no control to programmer over a time a thread should keep waiting for lock.
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.
Using ReentrantLock which was introduced in jdk 1.5 under java.util.concurrent.locks package, We can provide a timeout till when thread should keep waiting for acquiring a lock and after that time thread will proceed with normal execution. this will give more control to threads while waiting for a lock instead of indefinitely waiting or getting blocked till lock is acquired.
Example: tryLock method,
Lock lock = new ReentrantLock(); lock.tryLock(long timeout, TimeUnit unit)Complete trylock example in Java
package javabypatel; import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; public class ThreadSafeArrayList<E> { public static void main(String[] args) throws Exception { final ReentrantLock lock1 = new ReentrantLock(); final ReentrantLock lock2 = new ReentrantLock(); String printerLock = "PrinterLock"; String scannerLock = "ScannerLock"; Runnable try1_2 = getRunnable(lock1, printerLock, lock2, scannerLock); Runnable try2_1 = getRunnable(lock2, scannerLock, lock1, printerLock); Thread t1 = new Thread(try1_2); t1.start(); Thread t2 = new Thread(try2_1); t2.start(); } private static Runnable getRunnable(final ReentrantLock lock1, final String lock1Name, final ReentrantLock lock2, final String lock2Name) { return new Runnable() { @Override public void run() { try { if (lock1.tryLock(5, TimeUnit.SECONDS)) { System.out.println(lock1Name + " acquired by thread " + Thread.currentThread()); Random rand = new Random(); if (lock2.tryLock(rand.nextInt(10), TimeUnit.SECONDS)) { System.out.println(lock2Name + " acquired by thread " + Thread.currentThread()); Thread.sleep(2000); } else { System.out.println("Could not acquire " + lock2Name + " by thread " + Thread.currentThread()); lock1.unlock(); System.out.println(lock1Name + " released by thread " + Thread.currentThread()); } } else { System.out.println("Unable to acquire " + lock1Name + " by thread " + Thread.currentThread()); } } catch (InterruptedException e) { System.out.println("I am interrupted" + Thread.currentThread()); } finally { if (lock1.isHeldByCurrentThread()) lock1.unlock(); if (lock2.isHeldByCurrentThread()) lock2.unlock(); } } }; } }Fairness policy:
When thread (say thread 1) calls synchronized method or synchronized block, it has to first acquire the monitor and then only can enter inside synchronized area. If monitor is acquired by other thread say thread 2 then thread 1 has to wait.
Also, there can be many threads(say thread 1, thread 5, thread 8 etc) waiting for the same monitor. what will happen when thread 2 release the lock on monitor, which thread will be the next to execute among thread 1, thread 5, thread 8 waiting for lock?
There is no guarantee which thread will get the control and that totally depends on scheduler, this leads to problem of thread (say thread 1) not getting monitor even though it is waiting for longest time among other threads for acquiring the lock and other thread (say thread 5) gets the monitor even if it just joined the waiting queue. ReentrantLock solves this problem by adding fairness policy while creating
ReentrantLock object.
While creating a ReentrantLock object, we can provide fairness property for making the lock fair. Fairness property provides lock to longest waiting thread, in case of contention. With fairness enabled, Threads get the lock in the order they requested it.
Example:
ReentrantLock lock = new ReentrantLock(true);
Note: Performance is degraded by fairness policy.
LockInterruptibly
When thread 1 calls the synchronized method or synchronized block, it will be blocked until lock on the monitor is available. programmer don't have control to resume the blocked thread.
ReentrantLock provides a method called lockInterruptibly(), which can be used to interrupt the thread when it is waiting for lock. so that it can no longer in blocked state indefinitely.
void lockInterruptibly()
If thread is able to acquire lock then this method increments lock hold count by 1.
If the lock is with another thread then the current thread waits until it gets the lock or some other thread interrupts the thread.
lockinterruptibly example in java:
Number of Threads blocked on monitor:
LockInterruptibly
When thread 1 calls the synchronized method or synchronized block, it will be blocked until lock on the monitor is available. programmer don't have control to resume the blocked thread.
ReentrantLock provides a method called lockInterruptibly(), which can be used to interrupt the thread when it is waiting for lock. so that it can no longer in blocked state indefinitely.
void lockInterruptibly()
If thread is able to acquire lock then this method increments lock hold count by 1.
If the lock is with another thread then the current thread waits until it gets the lock or some other thread interrupts the thread.
lockinterruptibly example in java:
public class LockInterruptiblyExample{ final ReentrantLock reentrantLock = new ReentrantLock(); public void performTask() { try { reentrantLock.lockInterruptibly(); //wait till thread get the lock or till other thread interrupts //and you can control further execution from catch block try { //..... } finally { reentrantLock.unlock(); } } catch (InterruptedException e) { e.printStackTrace(); } } }
Number of Threads blocked on monitor:
Using synchronized method or block doesn't provide any mechanism to know how many threads are blocked to acquire a lock on monitor
Reentrant lock provides getQueueLength() method which return number of threads that may be waiting to acquire this lock in java.
Lock Unlock in different scope
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.
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
Advanced Java Multithreading Interview Questions & Answers
Type Casting Interview Questions and Answers In Java?
Exception Handling Interview Question-Answer
Method Overloading - Method Hiding Interview Question-Answer
How is ambiguous overloaded method call resolved in java?
Method Overriding rules in Java
Interface interview questions and answers in Java
Enjoy !!!!
If you find any issue in post or face any error while implementing, Please comment.
No comments:
Post a Comment