Tags
In java we have two types of locks available
1. Intrinsic Lock
2. Explicit Lock
Whenever we synchronize any object we get an Intrinsic Lock on that object. There is one more way to implement locking/synchronizing i.e. using Explicit Locks. From Java 1.5 java.util.concurrent.locks package provides some explicit locking classes which can be used to replace the the intrinsic locks. One of commonly used example of explicit lock is ReentrantLock.
There is a fair difference between throughput of Reentrant Lock over Intrinsic Lock in Java 1.5. That means the throughput of application using intrinsic lock goes down drastically as compared to Reentrant Lock in case of increasing no of threads. This gap was fixed in 1.6 and now both behave quite same in even in multiple thread scenario.
This was a brief description about intrinsic and explicit locks in Java. Now there is question is when to use explicit and when to use intrinsic. There are many reasons to use explicit reasons. One of them is tryLock() method which helps to prevent the deadlock scenarion. I ll we discussing this in later blogs. But for now I am going to use a example to show ‘How can we ensure the thread scheduling fairness between threads using Explicit Locks‘.
So out problem is that in case of multiple threads asking for a shared lock, we want to ensure that the thread get the lock the longest thread in waiting on the lock object.
We will start with using intrinsic lock and see how it behaves, in case of multiple threads. In this example we have a Simple Monitor Class which we will using for Locking and it will be shared among threads.
Class : Monitor
package com.intrinsiclock; public class Monitor { }
As we see here it just a blank class, which we will be using to share between threads as a common lock object.
The other class in this example is out thread class:
Class : MyThread
package com.intrinsiclock; public class MyThread implements Runnable { private Monitor monitor; public MyThread(Monitor monitor) { super(); this.monitor = monitor; } @Override public void run() { printMessage("Entered run method...trying to lock monitor object"); synchronized (monitor) { printMessage("Locked monitor object"); try{ Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } printMessage("Releasing lock"); monitor.notifyAll(); } printMessage("End of run method"); } private void printMessage(String msg){ System.out.println(Thread.currentThread().getName()+" : "+msg); } }
This class has implemented run method, and get shared lock in its constructor. It tries to lock monitor using synchronise block on monitor. Once it is able to lock it sleeps for some time just to ensure other threads tries to lock this monitor. Once it wakes up it just release lock by notifying all other waiting threads. We have added simple print messages to show where the threads are and what they are doing.
And finally our main class which will invoke multiple threads contending for the locks:
Class: IntrinsicTest
package com.intrinsiclock; public class IntrinsicTest { public static void main(String[] args) { System.out.println("=========Intrinsic Lock Test======="); String[] myThreads = {"Therad ONE","Thread TWO","Thread THREE","Thread FOUR"}; Monitor monitor = new Monitor(); for(String threadName:myThreads) { new Thread(new MyThread(monitor),threadName).start(); } } }
One we run this example we get following output:
=========Intrinsic Lock Test======= Therad ONE : Entered run method...trying to lock monitor object Therad ONE : Locked monitor object Thread THREE : Entered run method...trying to lock monitor object Thread TWO : Entered run method...trying to lock monitor object Thread FOUR : Entered run method...trying to lock monitor object Therad ONE : Releasing lock Therad ONE : End of run method Thread FOUR : Locked monitor object Thread FOUR : Releasing lock Thread FOUR : End of run method Thread TWO : Locked monitor object Thread TWO : Releasing lock Thread TWO : End of run method Thread THREE : Locked monitor object Thread THREE : Releasing lock Thread THREE : End of run method
The output of this program will be different each time we run this code. Let me explain you the reason. As we here the Thread ONE has acquired the lock in the first place and meanwhile the Thread TWO,THREE and FOUR also came in and requested for lock. The order in which they came are THREE, TWO and FOUR. But when we see the output of the code, it is FOUR who got lock first followed by TWO and THREE. So basically FOUR jumped out of line and grabbed the lock once it was released by ONE.
The reason is notifyAll(). Actually the methods notify() and notifyAll() does not guarantee which one of the waiting thread will get lock. That is the reason we will get random sequence in which the threads gets lock in this example.
This is not what we wanted to achieve, but we wanted the thread who requested for lock first should get the lock first. But it is not possible in case of using intrinsic locks. So here comes the Explicit Locks for our help. The ReentrantLock has a special flag called fairness. By making ‘fair=true‘ it ensures that the lock in granted to the thread who requested first. By default ReentrantLock fair flag is false i.e. same behavior as intrinsic lock. But by just passing one argument to the constructor to the ReentrantLock class we can achieve the fairness we are looking for.
Lock lock = new ReentrantLock(true);
Lets take a example to show this. So our first class in our thread class
Class : MyThread
package com.explicitlock; import java.util.concurrent.locks.Lock; public class MyThread implements Runnable { private Lock lock; public MyThread(Lock lock) { super(); this.lock = lock; } @Override public void run() { printMessage("Entered run method...trying to lock monitor object"); lock.lock(); try{ printMessage("Locked monitor object"); try{ Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } finally{ printMessage("Realising lock"); lock.unlock(); } printMessage("End of run method"); } private void printMessage(String msg){ System.out.println(Thread.currentThread().getName()+" : "+msg); } }
As we can see here we are using Lock object to achieve synchronization in this thread class. The lock.lock() method is a blocking method. It blocks the current thread untill it gets the lock. Once it gets the lock it does same stuff as our previous example, sleeps for some time and release lock. Interestingly we are calling unlock() in finally block, this is very important to ensure that lock is release even the thread exist due to some abnormal termination. Otherwise its very difficult to detect such leaks when a thread died with un-released lock.
Now lets see the main class which invokes multiple thread with one shared lock, which all of them tries to lock:
Class : ExplicitLockTest
package com.explicitlock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ExplicitLockTest { public static void main(String[] args) { System.out.println("=========Explicit Lock Test======="); String[] myThreads = {"Therad ONE","Thread TWO","Thread THREE","Thread FOUR"}; Lock lock = new ReentrantLock(true); for(String threadName:myThreads) { new Thread(new MyThread(lock),threadName).start(); } } }
Here we have created a Lock object of ReentrantLock and shared among all threads. The catch is passing fair flag while creating the lock object:
Lock lock = new ReentrantLock(true);
Now lets run the code
=========Explicit Lock Test======= Therad ONE : Entered run method...trying to lock monitor object Therad ONE : Locked monitor object Thread TWO : Entered run method...trying to lock monitor object Thread THREE : Entered run method...trying to lock monitor object Thread FOUR : Entered run method...trying to lock monitor object Therad ONE : Realising lock Therad ONE : End of run method Thread TWO : Locked monitor object Thread TWO : Realising lock Thread TWO : End of run method Thread THREE : Locked monitor object Thread THREE : Realising lock Thread THREE : End of run method Thread FOUR : Locked monitor object Thread FOUR : Realising lock Thread FOUR : End of run method
Here we can see Thread ONE has acquired the lock first meanwhile the Thread TWO, THREE and FOUR also came in and requested for the lock. So once the lock got released by ONE it was allocated to TWO followed by THREE and FOUR. And this lock ensures that the thread who requested lock first get the lock first whenever available.
The catch is
Lock lock = new ReentrantLock(true);
If we don’t pass the the fair flag here, then it behaves same as intrinsic locks. But using fair flag achieves the fairness in thread lock allocation.
Note: Using fair flag results into drop of throughput of the application. Hence we should not be using anyway but whenever the the fairness is required.