The problems with traditional synchronized keyword:

1. We are not having any flexibity to try for the lock without waiting.
2. There is no way to specify maximum waiting time for a Thread to get lock so that Thread will wait until getting the lock which may create performance problem and cause deadlock.
3. If a Thread releases the lock, then which waiting Thread will get that lock we are not having any control on this.
4. There is no API to list out all waiting Threads for a lock.
5. The synchronized compulsory we have to use either or method level or with in the method and it is not possible to use across the multiple methods.
To overcome these problems, java people introduced java.util.concurrent.locks package in jdk1.5 version It also provides several enhancements to the programmer to provide more control on concurrency.

Lock interface:

Lock object is similar to implicit lock acquired by a Thread to execute synchronized method or synchronized block.
Lock implementations provide more extensive operations than traditional implicit locks.


Important methods of Lock interface:

void lock(): we can use this method to acquire a lock. If the lock is already available then immediately current Thread will get that lock. If the lock is not already available then it will wait until getting the lock. It is same behavior of traditional synchronized keyword.

boolean tryLock(): to acquire the lock without waiting. If the lock is available then the Thread acquires the lock and returns the true. if the lock is not available then the lock returns false and can continue its execution without waiting. In this case Thread will never be entered into waiting state.


if(l.tryLock()){
    perform safe operations;
}
else{
    perform alternative operations;
}

3. boolean tryLock(long time, TimeUnit unit):
if the lock is available then the Thread will get the lock and continue its execution. if the lock is not available then the Thread will wait until specified amount of time still if the lock is not available then Thread can continue its execution.
TimeUnit is an enum present in java.util.concurrent package;

enum TimeUnit{
    NANOSECONDS;
    MICROSECONDS;
    MILLISECONDS; 
    SECONDS; 
    MINUTES; 
    HOURS; 
    DAYS;
}
if(l.tryLock(1000TimeUnit MILLISECONDS)){

}

4. void lockinterruptibly(): acquires the lock if it is available and returns immediately. If the lock is not available then it will wait. While waiting if the Thread is interrupted then Thread won’t get the lock.

5. void unlock(): To call this method compulsory current Thread should be owner of that lock otherwise we will get runtime exceptions saying IllegalMonitorStateException.


ReentrantLock():

  • It is the implementation class of Lock interface and it is direct child class of Object.
  • Reentrant means a Thread can acquire same lock multiple times without any issue.
  • Internally Reentrant lock implements Threads personal count whenever we called lock(0 method and decrements count value whenever thread call unlock() method and lock will released whenever count reaches 0.

Constructors:

1.  ReentrantLock l = new ReentrantLock();

creates an instance of ReentrantLock


2.  ReentrantLock l = new ReentrantLock(boolean fairness);

creates ReentrantLock with a given fairness policy.
If the fairness is true then longest waiting thread can acquire the lock if it is available i.e., it follows first come first serve policy.
If the fairness is false then which waiting Thread will get chance we cant expect.

Note: the default value for fairness is false.

which of the following declarations are equal?

[1]. ReentranLock l = new ReentrantLock();
[2]. ReentranLock l = new ReentrantLock(true);
[3]. ReentranLock l = new ReentrantLock(false);
[4]. All the above

Important methods of ReentrantLock:

void lock()

boolean tryLock()

boolean tryLock(long l, TimeUnit t)

void lock Interruptibly()

void unlock()

int getHoldCount(): returns number of holds on the lock by current Thread

boolean isHeldByCurrentThread(): returns true if and only if lock keys hold by current Thread

int getQueueLength(): returns no of Threads waiting for the lock

Collection getQueuedThreads(): It returns a collection of Threads which are waiting to get the lock.

boolean hasQueuedThreads(): returns true if any Thread waiting to get the lock

boolean isLocked(): returns true if the lock is acquired by some Thread

boolean isFair(): returns true if the fairness policy is set with true value 

Thread getOwner(): returns the Thread which acquires the lock.


import java.util.concurrent.locks.*;
class ReentrantLockDemo{
    public static void main(String[] args){
        ReentrantLock l = new ReentrantLock();
        l.lock();
        l.lock(); 
        System.out.println(l.isLocked()); //true 
        System.out.println(l.isHeldByCurrentThread()); //true
        System.out.println(l.getQueueLength()); //0 
        l.unlock(); 
        System.out.println(l.getHoldCount()); //1 
        System.out.println(l.isLocked()); //true 
        l.unlock(); 
        System.out.println(l.isLocked()); //false 
        System.out.println(l.isFair()); //false
    }
}

import java.util.concurrent.locks.*;
class Display{
    ReentrantLock l = new ReentrantLock();
    public void wish(String name){
        l.lock(); //line-1
        for(int i = 0; i < 5; i++){
            System.out.println("Good Morning :");
            try{
               Thread.sleep(2000);
            }
            catch(InterruptedException e){ 
            }
            System.out.println(name);
            }
        l.unlock(); // line-2
    }
}
class MyThread extends Thread{
    Display d;
    String name;
    MyThread(Display dString name){
        this.d = d;
        this.name = name;
    }
    public void run(){
        d.wish(name);
    }
}
class ReentrantLockDemo{
    public static void main(String[] args){
        Display d = new Display();
        MyThread t1 = new MyThread(d, "Dhoni");
        MyThread t2 = new MyThread(d, "Rohit"); 
        t1.start(); 
        t2.start();
    }
}

if we comment line-1 and line-2 then the Threads will be executed simultaneously and we will get irregular output
if we are not comment line-1 and line-2 then the Threads will be executed one by one and we will get regular output


//Program for tryLock() method
import java.util.concurrent.locks.*;
class MyThread extends Thread{
    static ReentrantLock l = new ReentrantLock();
    MyThread(String name){ 
        super(name);
    }
    public void run(){
        if(l.tryLock()){
            System.out.println(Thread.currentThread().getName() + "...got lock and performing safe operations");
            try{
                Thread.sleep(2000);
            }
            catch(InterruptedException e){
            }
            l.unlock();
        }
        else{
            System.out.println(Thread.currentThread().getName() + "...unable to get lock and hence performing alternative operations");
        }
    }
}
class ReentrantLockDemo{
    public static void main(String[] args){
        MyThread t1 = new MyThread("First Thread");
        MyThread t2 = new MyThread("Second Thread");
        t1.start();
        t2.start();
    }
}

Output:
First Thread...got lock and performing safe operations
Second Thread...unable to get lock and hence performing alternative operations


//Program for tryLock() method 
import java.util.concurrent.locks.*; 
import java.util.concurrent.*;
class MyThread extends Thread{
    static ReentrantLock l = new ReentrantLock();
    MyThread(String name){
        super(name);
    }
    public void run(){
        do{
            try{
                if(l.tryLock(5000TimeUnit.MILLISECONDS)){
                    System.out.println(Thread.currentThread().getName() + "...got lock");
                    Thread.sleep(10000);
                    l.unlock();
                    System.out.println(Thread.currentThread().getName() + "...releases lock");
                    break;
                }
                else{
                    System.out.println(Thread.currentThread().getName() + "...unable to get lock and will try again");
                }
            }
            catch(InterruptedException e){
            }
        }while(true);
    }
}
class ReentrantLockDemo{
    public static void main(String[] args){
        MyThread t1 = new MyThread("First Thread");
        MyThread t2 = new MyThread("Second Thread"); 
        t1.start(); 
        t2.start();
    }
}

Output:
First Thread...got lock
Second Thread...unable to get lock and will try again
First Thread...releases lock
Second Thread...got lock
Second Thread...releases lock