Reentrant Lock in Java
The traditional way to achieve thread synchronization in Java is by the use of synchronized keyword. While it provides a certain basic synchronization, the synchronized keyword is quite rigid in its use. For example, a thread can take a lock only once. Synchronized blocks don’t offer any mechanism of a waiting queue and after the exit of one thread, any thread can take the lock. This could lead to starvation of resources for some other thread for a very long period of time.
Reentrant Locks are provided in Java to provide synchronization with greater flexibility.
What are Reentrant Locks?
The ReentrantLock class implements the Lock interface and provides synchronization to methods while accessing shared resources. The code which manipulates the shared resource is surrounded by calls to lock and unlock method. This gives a lock to the current working thread and blocks all other threads which are trying to take a lock on the shared resource.
As the name says, ReentrantLock allows threads to enter into the lock on a resource more than once. When the thread first enters into the lock, a hold count is set to one. Before unlocking the thread can re-enter into lock again and every time hold count is incremented by one. For every unlocks request, hold count is decremented by one and when hold count is 0, the resource is unlocked.
Reentrant Locks also offer a fairness parameter, by which the lock would abide by the order of the lock request i.e. after a thread unlocks the resource, the lock would go to the thread which has been waiting for the longest time. This fairness mode is set up by passing true to the constructor of the lock.
These locks are used in the following way:
Unlock statement is always called in the finally block to ensure that the lock is released even if an exception is thrown in the method body(try block).
- lock(): Call to the lock() method increments the hold count by 1 and gives the lock to the thread if the shared resource is initially free.
- unlock(): Call to the unlock() method decrements the hold count by 1. When this count reaches zero, the resource is released.
- tryLock(): If the resource is not held by any other thread, then call to tryLock() returns true and the hold count is incremented by one. If the resource is not free, then the method returns false, and the thread is not blocked, but exits.
- tryLock(long timeout, TimeUnit unit): As per the method, 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(): This method acquires the lock if the resource is free while allowing for the thread to be interrupted by some other thread while acquiring the resource. It means that if the current thread is waiting for the lock but some other thread requests the lock, then the current thread will be interrupted and return immediately without acquiring the lock.
- getHoldCount(): This method returns the count of the number of locks held on the resource.
- isHeldByCurrentThread(): This method returns true if the lock on the resource is held by the current thread.
In the following tutorial, we will look at a basic example of Reentrant Locks.
Steps to be followed
1. Create an object of ReentrantLock 2. Create a worker(Runnable Object) to execute and pass the lock to the object 3. Use the lock() method to acquire the lock on shared resource 4. After completing work, call unlock() method to release the lock
Below is the implementation of problem statement:
Output: task name - Job2 waiting for lock task name - Job1 outer lock acquired at 09:49:42 Doing outer work task name - Job2 waiting for lock task name - Job1 inner lock acquired at 09:49:44 Doing inner work Lock Hold Count - 2 task name - Job2 waiting for lock task name - Job2 waiting for lock task name - Job1 releasing inner lock Lock Hold Count - 1 task name - Job1 work done task name - Job1 releasing outer lock Lock Hold Count - 0 task name - Job3 outer lock acquired at 09:49:45 Doing outer work task name - Job2 waiting for lock task name - Job3 inner lock acquired at 09:49:47 Doing inner work Lock Hold Count - 2 task name - Job2 waiting for lock task name - Job2 waiting for lock task name - Job3 releasing inner lock Lock Hold Count - 1 task name - Job3 work done task name - Job3 releasing outer lock Lock Hold Count - 0 task name - Job4 outer lock acquired at 09:49:48 Doing outer work task name - Job2 waiting for lock task name - Job4 inner lock acquired at 09:49:50 Doing inner work Lock Hold Count - 2 task name - Job2 waiting for lock task name - Job2 waiting for lock task name - Job4 releasing inner lock Lock Hold Count - 1 task name - Job4 work done task name - Job4 releasing outer lock Lock Hold Count - 0 task name - Job2 outer lock acquired at 09:49:52 Doing outer work task name - Job2 inner lock acquired at 09:49:53 Doing inner work Lock Hold Count - 2 task name - Job2 releasing inner lock Lock Hold Count - 1 task name - Job2 work done task name - Job2 releasing outer lock Lock Hold Count - 0
- One can forget to call the unlock() method in the finally 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.
The ReentrantLock is a better replacement for synchronization, which offers many features not provided by synchronized. However, the existence of these obvious benefits are not a good enough reason to always prefer ReentrantLock to synchronize. Instead, make the decision on the basis of whether you need the flexibility offered by a ReentrantLock.
This article is contributed by Abhishek. If you like GeeksforGeeks and would like to contribute, you can also write an article using contribute.geeksforgeeks.org or mail your article to firstname.lastname@example.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.
Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above.