Friday, September 15, 2023

What is ReentrantLock in Java? Difference between synchronized vs ReentrantLock with Example

ReentrantLock in Java is added on java.util.concurrent package in Java 1.5 along with other concurrent utilities like CountDownLatch, Executors, and CyclicBarrier. ReentrantLock is one of the most useful additions in Java concurrency package and several of concurrent collection classes from java.util.concurrent package is written using ReentrantLock, including ConcurrentHashMap, see How ConcurrentHashMap works in Java for more details. Two key feature of ReentrantLock, which provides more control on lock acquisition is trying to get a lock with the ability to interrupt, and a timeout on waiting for a lock, these are key for writing responsive and scalable systems in Java.


In short, ReentrantLock extends the functionality of synchronized keyword in Java and open the path for more controlled locking in Java. 

In this Java concurrency tutorial we will learn :
  • What is ReentrantLock in Java?
  • Difference between ReentrantLock and synchronized keyword in Java?
  • Benefits of using Reentrant lock in Java?
  • Drawbacks of using Reentrant lock-in concurrent program?
  • Code Example of ReentrantLock in Java?


1. What is ReentrantLock in Java?

On class level,  ReentrantLock is a concrete implementation of the Lock interface provided in the Java concurrency package from Java 1.5 onwards.  

As per Javadoc, ReentrantLock is a mutual exclusive lock, similar to implicit locking provided by synchronized keyword in Java, with extended features like fairness, which can be used to provide lock to longest waiting thread. 

A lock is acquired by the lock() method and held by Thread until a call to unlock() method. The fairness parameter is provided while creating the instance of ReentrantLock in the constructor. ReentrantLock provides the same visibility and ordering guarantee, provided by implicitly locking, which means, unlock() happens before another thread gets lock(). 

And, if you are not familiar with key threading concepts in Java like happens-before then you can also see these Java Multithreading courses to learn more about locking and synchronization. 




2. Difference between ReentrantLock and synchronized keyword in Java

Though ReentrantLock provides the same visibility and orderings guaranteed as an implicit lock, acquired by synchronized keyword in Java, it provides more functionality and differs in certain aspects. 

As stated earlier, the main difference between synchronized and ReentrantLock is the ability to trying to lock interruptibly, and with a timeout. The thread doesn’t need to block infinitely, which was the case with synchronized. Let’s see a few more differences between synchronized and Lock-in Java.



1) Another significant difference between ReentrantLock and the synchronized keyword is fairness. The synchronized keyword doesn't support fairness. Any thread can acquire lock once released, no preference can be specified, on the other hand, you can make ReentrantLock fair by specifying fairness property while creating an instance of ReentrantLock. Fairness property provides a lock to the longest waiting thread, in case of contention.

2) The second difference between synchronized and Reentrant lock is tryLock() method. ReentrantLock provides a convenient tryLock() method, which acquires lock only if its available or not held by any other thread. This reduces the blocking of thread waiting for lock-in Java applications.

3) One more worth noting the difference between ReentrantLock and synchronized keyword in Java is, the ability to interrupt Thread while waiting for Lock

In case of a synchronized keyword, a thread can be blocked waiting for a lock, for an indefinite period of time and there was no way to control that. 

ReentrantLock provides a method called lockInterruptibly(), which can be used to interrupt thread when it is waiting for lock. Similarly, tryLock() with timeout can be used to timeout if the lock is not available in certain time period.

4) ReentrantLock also provides convenient method to get List of all threads waiting for lock.

So, you can see, lot of significant differences between synchronized keyword and ReentrantLock in Java. In short, Lock interface adds lot of power and flexibility and allows some control over lock acquisition process, which can be leveraged to write highly scalable systems in Java.

2.1 Benefits of ReentrantLock in Java

Most of the benefits derives from the differences covered between synchronized vs ReentrantLock in last section. Here is summary of benefits offered by ReentrantLock over synchronized in Java:

1) Ability to lock interruptibly.  Which means you can interrupt the thread while its waiting for lock. 
2) Ability to timeout while waiting for lock.
3) Power to create fair lock.
4) API to get list of waiting thread for lock.
5) Flexibility to try for lock without blocking.



2.2 Disadvantages of ReentrantLock in Java

Major drawback of using ReentrantLock in Java is wrapping method body inside try-finally block, which makes code unreadable and hides business logic. It’s really cluttered and I hate it most, though IDE like Eclipse and Netbeans can add those try catch block for you. 

Another disadvantage is that, now programmer is responsible for acquiring and releasing lock, which is a power but also opens gate for new subtle bugs, when programmer forget to release the lock in finally block.

3. Lock and ReentrantLock Example in Java

Here is a complete code example of How to use Lock interface and ReentrantLock in Java. This program locks a method called getCount(), which provides a unique count to each caller. Here we will see both synchronized and ReentrantLock versions of the same program. You can see code with synchronized is more readable but it’s not as flexible as locking mechanism provided by Lock interface.

import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Java program to show, how to use ReentrantLock in Java.
 * Reentrant lock is an alternative way of locking
 * apart from implicit locking provided by synchronized keyword in Java.
 *
 * @author  Javin Paul
 */
public class ReentrantLockHowto {

    private final ReentrantLock lock = new ReentrantLock();
    private int count = 0;

     //Locking using Lock and ReentrantLock
     public int getCount() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " gets Count: " + count);
            return count++;
        } finally {
            lock.unlock();
        }
     }

     //Implicit locking using synchronized keyword
     public synchronized int getCountTwo() {
            return count++;
     }

    

    public static void main(String args[]) {
        final ThreadTest counter = new ThreadTest();
        Thread t1 = new Thread() {

            @Override
            public void run() {
                while (counter.getCount() < 6) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();                    }
                }
            }
        };
      
        Thread t2 = new Thread() {

            @Override
            public void run() {
                while (counter.getCount() < 6) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                }
            }
        };
      
        t1.start();
        t2.start();
      
    }
}

Output:
Thread-0 gets Count: 0
Thread-1 gets Count: 1
Thread-1 gets Count: 2
Thread-0 gets Count: 3
Thread-1 gets Count: 4
Thread-0 gets Count: 5
Thread-0 gets Count: 6
Thread-1 gets Count: 7

That’s all on What is ReentrantLock in Java, How to use with a simple example, and the difference between ReentrantLock and synchronized keyword in Java. We have also seen significant enhancement provided by Lock interface over synchronized e.g. trying for the lock, timeout while waiting for lock and ability to interrupt thread while waiting for the lock. Just be careful to release lock in finally block.


Related Java multithreading and concurrency tutorials from Javarevisited Blog


And lastly, one question for you? What is difference between ReentrantLock and ReadWriteLock in Java? Can you use ReadWriteLock in place of ReentrantLock in Java? 

15 comments:

  1. Javin please edit the program I think you have missed to put the synchronized keyword ..it should be ..

    //Implicit locking using synchronized keyword
    public int synchronized getCountTwo() {
    return count++;
    }


    ReplyDelete
  2. Reentrant lock functionality :-
    ============================

    A reentrant lock will allow the lock holder to enter blocks of code even after it has already obtained the lock by entering other blocks of code. A non-reentrant lock would have the lock holder block on itself as it would have to release the lock it obtained from another block of code to reobtain that same lock to enter the nested lock requiring block of code

    public synchronized void functionOne() {

    // do something

    functionTwo();

    // do something else

    // redundant, but permitted...
    synchronized(this) {
    // do more stuff
    }
    }

    public synchronized void functionTwo() {
    // do even more stuff!
    }



    Extended capabilities of reentrant lock include :-
    ===============================================

    1) The ability to have more than one condition variable per monitor. Monitors that use the synchronized keyword can only have one. This means reentrant locks support more than one wait()/notify() queue.
    2) The ability to make the lock "fair". "[fair] locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order." Synchronized blocks are unfair.
    3) The ability to check if the lock is being held.
    4) The ability to get the list of threads waiting on the lock.


    Disadvantages of reentrant locks are :-
    ====================================

    Need to add import statement.
    Need to wrap lock acquisitions in a try/finally block. This makes it more ugly than the synchronized keyword.
    The synchronized keyword can be put in method definitions which avoids the need for a block which reduces nesting.


    When to use :-
    ===========

    1. ReentrantLock might be more apt to use if you need to implement a thread that traverses a linked list, locking the next node and then unlocking the current node.
    2. Synchronized keyword is apt in situation such as lock coarsening, provides adaptive spinning,biased locking and the potential for lock elision via escape analysis. Those optimizations aren't currently implemented for ReentrantLock.

    ReplyDelete
  3. @Saral, great comment. In context of allowing thread to reenter to a block of code, which is also locked by same lock, implicit locking by synchronized keyword also provides same functionality. In short, implicit lock is also re-entrant. I think, ReentrantLock should be prefer over synchronized for high performance code, you can see almost all classes on java.util.concurrent uses ReentrantLock.

    ReplyDelete
  4. I guess most important difference between a synchronized lock and a ReentrantLock is to release lock in a different code block. With ReentrantLock, you can acquire lock in one method and can release in other method, but synchronized keyword forces you to release lock in same code block, doesn't matter if you synchronized method or block.

    ReplyDelete
  5. I ran few example with almost 50 threads kicking at same time (also No sleep call on thread ) using reentarant lock and found that thread were still entering in block though other thread already already acquired a lock. Problem resolved when i create the reentrant object as static final (private static final ReentrantLock rLock = new ReentrantLock()) .

    ReplyDelete
  6. public class RentractLockHowTo extends Thread{

    private static final ReentrantLock lock =new ReentrantLock();
    int counter =0;;
    @Override
    public void run(){

    try{
    lock.tryLock();
    System.out.println(Thread.currentThread().getName());
    System.out.println(counter++);

    }finally{
    //lock.unlock();
    }
    }

    public static void main(String[] args) throws Exception{

    RentractLockHowTo tes = new RentractLockHowTo();
    Thread t1= new Thread(tes);
    Thread t2 = new Thread(tes);
    RentractLockHowTo t = new RentractLockHowTo();
    Thread t3 = new Thread(t);
    Thread t4 = new Thread(t);
    Thread t5 = new Thread(t);

    t1.start();

    t2.start();
    //Thread.sleep(1000);
    t3.start();
    t4.start();
    t5.start();
    }

    I wrote this class.But when i put a debug Point at lock.tryLock(),allthe 6 threads entered at the same time in the tryLock block.Can u tell me why this happens that all the 6 locks are entered into the Lock block .

    ReplyDelete
  7. public void run() {

    try {
    lock.lock();//<-----

    System.out.println(Thread.currentThread().getName());
    System.out.println(counter++);

    } finally {
    lock.unlock();
    }
    }

    ReplyDelete
  8. Hi Javin,
    i have a doubt regarding your code written below.

    Thread t1 = new Thread() {

    @Override
    public void run() {
    while (counter.getCount() < 6) {
    try {
    Thread.sleep(100);
    } catch (InterruptedException ex) {
    ex.printStackTrace(); }
    }
    }
    };

    can wr write a single run() method for both the threads..? i tried to to that by creating a new class extending Thread class and override the run() method same as above but out put is not coming same. i mean count value is repeating for thread 1 and thread 2. please Explain....

    ReplyDelete
  9. Where is class "ThreadTest" ?
    I also do not see class "ReentrantLockHowto" being used ?
    I am getting it right ?...can someone please explain...thanks in advance.

    ReplyDelete
  10. correct code would be ....

    package reentrant;

    /**
    *
    * @author avishek
    */
    import java.util.concurrent.locks.ReentrantLock;
    import java.util.logging.Level;
    import java.util.logging.Logger;

    /**
    * Java program to show, how to use ReentrantLock in Java. Reentrant lock is an
    * alternative way of locking apart from implicit locking provided by
    * synchronized keyword in Java.
    *
    * @author Javin Paul
    */
    public class ReentrantLockHowto {

    // @Override
    // public void run() {
    // throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    // }
    static class ThreadTest implements Runnable {

    private final ReentrantLock lock = new ReentrantLock();
    private int count = 0;

    public void run() {
    while (true) {
    try {
    getCount();
    Thread.sleep(100);
    } catch (InterruptedException ex) {
    ex.printStackTrace();
    }
    }
    }

    //Locking using Lock and ReentrantLock

    public int getCount() {
    lock.lock();
    try {
    System.out.println(Thread.currentThread().getName() + " gets Count: " + count);
    return count++;
    } finally {
    lock.unlock();
    }
    }

    //Implicit locking using synchronized keyword
    public synchronized int getCountTwo() {
    return count++;
    }

    }

    public static void main (String args[]) {
    final ThreadTest counter = new ThreadTest();
    Thread t1 = new Thread(counter);
    Thread t2 = new Thread(counter);
    t1.start();
    t2.start();

    }
    }

    ReplyDelete
  11. I fail to understand the difference between reententrantLock and synchronized lock based on the out of the sample program.What does Thread 1 or Thread 0 printing the count two times consequently mean.Thanks

    ReplyDelete
  12. @Anonymous, the program shows how you can safely share a counter between two threads. Synchronization and thread-safety is provided by both synchronized keyword and ReentrantLock. If you see there are two methods getCount() and getCountTwo(), first one is using ReentrantLock and second one is using synchronized keyword, but both are protecting the counter correctly.

    Key is that counter should be accessed simulteneously by two threads and that's what happening in program.

    had there been any problem with ReentrantLock or synchronization, you might see counts repeated or jumping e.g. 1, 2, 2, 3, 5, 6 etc. Since counts are in sequence they are working properly.

    ReplyDelete
  13. It should be final ReentrantLockHowto counter = new ReentrantLockHowto();

    in place of final ThreadTest counter = new ThreadTest();

    ReplyDelete
  14. What type of lock acquired by ReenterentLock ? If it is Object lock then how we can achieve Class level lock or vice-versa .

    ReplyDelete
  15. Do you mean ReentrantLock interface of java.util.concurrent or you just talking about re-entrant lock concept? In case of later, you don't need to acquire lock if you already have one needed for a method. In case of former, its object lock on Lock class, Java doc also has some useful info https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/locks/ReentrantLock.html

    ReplyDelete