🎉 Special Offer !    Code: GET300OFF    Flat ₹300 OFF on every Java Course
Grab Deal 🚀

ReentrantLock in Java  


Introduction
  • ReentrantLock is a class in the java.util.concurrent.locks package.
  • It is an explicit lock that provides the same basic behavior as the synchronized block but with additional features.
  • It is called “reentrant” because the same thread can acquire the lock multiple times without blocking itself.
  • Provides more control than the synchronized keyword, such as tryLock() with timeout, interruptible locks, and multiple condition variables.
  • Uses of ReentrantLock:
    1. Thread Synchronization:
      • Ensures only one thread can access a critical section at a time.
    2. Avoid Deadlocks:
      • With tryLock(), threads can attempt to acquire a lock without waiting indefinitely.
    3. Fairness Control:
      • Can be configured to grant access to threads in FIFO order.
    4. Multiple Condition Variables:
      • Unlike synchronized, allows multiple Condition objects for finer-grained signaling between threads.
    5. Interruptible Locks:
      • Threads waiting to acquire a lock can be interrupted, which is not possible with synchronized.
    6. Better Performance:
      • Can improve throughput in complex multithreaded programs compared to synchronized.
Methods of ReentrantLock Class:
  • Below are some ReenterantLock class methods which are mostly used:
    Method Description
    lock() Acquires the lock. If the lock is not available, the thread waits indefinitely until it is free.
    unlock() Releases the lock held by the current thread. Must be called in a finally block to avoid deadlocks.
    tryLock() Tries to acquire the lock immediately. Returns true if successful, otherwise false. Useful to avoid blocking a thread.
    tryLock(long timeout, TimeUnit unit) Tries to acquire the lock within the given timeout. Returns true if successful, otherwise false. Helps avoid deadlocks.
    lockInterruptibly() Acquires the lock unless the thread is interrupted while waiting. Allows a thread to respond to interrupts.
    isLocked() Returns true if the lock is held by any thread.
    isHeldByCurrentThread() Returns true if the current thread holds the lock.
    getHoldCount() Returns the number of times the current thread has acquired the lock.
Program 1:
  • Below is the simple program using lock(), unlock(), isLocked(), isHeldByCurrentThread() and getHoldCount() methods:
    import java.util.concurrent.locks.ReentrantLock;
    
    class SimpleLockDemo
    {
        private final ReentrantLock lock = new ReentrantLock();
    
        public void doWork()
        {
            lock.lock(); // Acquire the lock
            try
            {
                System.out.println(Thread.currentThread().getName() + " acquired the lock.");
                System.out.println("Is lock held by any thread? " + lock.isLocked());
                System.out.println("Is lock held by current thread? " + lock.isHeldByCurrentThread());
                System.out.println("Hold count: " + lock.getHoldCount());
    
                // Simulate some work
                Thread.sleep(1000);
            }
            catch (InterruptedException e)
            {
                System.out.println(Thread.currentThread().getName() + " was interrupted.");
            }
            finally
            {
                lock.unlock(); // Release the lock
                System.out.println(Thread.currentThread().getName() + " released the lock.");
            }
        }
    }
    
    // Thread subclass for using the lock
    class WorkThread extends Thread
    {
        SimpleLockDemo demo;
    
        public WorkThread(SimpleLockDemo demo, String name)
        {
            super(name);
            this.demo = demo;
        }
    
        @Override
        public void run()
        {
            demo.doWork();
        }
    }
    
    public class MainApp4
    {
        public static void main(String[] args)
        {
            SimpleLockDemo demo = new SimpleLockDemo();
    
            WorkThread t1 = new WorkThread(demo, "Thread-1");
            WorkThread t2 = new WorkThread(demo, "Thread-2");
    
            t1.start();
            t2.start();
        }
    }
Program 2:
  • Below is the program showing how to remove deadlock problem :
    import java.util.concurrent.locks.ReentrantLock;
    import java.util.concurrent.TimeUnit;
    
    class Resource
    {
        // any custom code
    }
    
    class MyThread1 extends Thread
    {
        private final ReentrantLock lock1;
        private final ReentrantLock lock2;
    
        MyThread1(ReentrantLock lock1, ReentrantLock lock2)
        {
            this.lock1 = lock1;
            this.lock2 = lock2;
        }
    
        @Override
        public void run()
        {
            try
            {
                if (lock1.tryLock(1, TimeUnit.SECONDS))  // Try to acquire lock1
                {
                    System.out.println("Thread 1 has locked Resource 1");
    
                    Thread.sleep(500); // Simulate some work
    
                    if (lock2.tryLock(1, TimeUnit.SECONDS))  // Try to acquire lock2
                    {
                        try
                        {
                            System.out.println("Thread 1 has locked Resource 2");
                        }
                        finally
                        {
                            lock2.unlock();
                            System.out.println("Thread 1 released Resource 2");
                        }
                    }
                    else
                    {
                        System.out.println("Thread 1 could not acquire Resource 2, avoiding deadlock!");
                    }
                    lock1.unlock();
                    System.out.println("Thread 1 released Resource 1");
                }
                else
                {
                    System.out.println("Thread 1 could not acquire Resource 1, skipping work!");
                }
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }
    
    class MyThread2 extends Thread
    {
        private final ReentrantLock lock1;
        private final ReentrantLock lock2;
    
        MyThread2(ReentrantLock lock1, ReentrantLock lock2)
        {
            this.lock1 = lock1;
            this.lock2 = lock2;
        }
    
        @Override
        public void run()
        {
            try
            {
                if (lock2.tryLock(1, TimeUnit.SECONDS))     // Try to acquire lock2 first
                {
                    System.out.println("Thread 2 has locked Resource 2");
    
                    Thread.sleep(500);
    
                    if (lock1.tryLock(1, TimeUnit.SECONDS))     // Try to acquire lock1
                    {
                        try
                        {
                            System.out.println("Thread 2 has locked Resource 1");
                        }
                        finally
                        {
                            lock1.unlock();
                            System.out.println("Thread 2 released Resource 1");
                        }
                    }
                    else
                    {
                        System.out.println("Thread 2 could not acquire Resource 1, avoiding deadlock!");
                    }
                    lock2.unlock();
                    System.out.println("Thread 2 released Resource 2");
                }
                else
                {
                    System.out.println("Thread 2 could not acquire Resource 2, skipping work!");
                }
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }
    
    public class DeadlockProblemSolved
    {
        public static void main(String[] args)
        {
            ReentrantLock lock1 = new ReentrantLock();
            ReentrantLock lock2 = new ReentrantLock();
    
            MyThread1 t1 = new MyThread1(lock1, lock2);
            MyThread2 t2 = new MyThread2(lock1, lock2);
    
            t1.start();
            t2.start();
        }
    }