Lock interface in Java



Lock

In this tutorial, we will see what is a Lock interface in Java, its implementation classes, and methods along with examples.

Lock interface in Java

Java lock interface

Lock interface is a synchronization technique that is available from JDK 1.5. It is similar to a synchronized block but is more flexible and complicated. The Lock interface is part of the java.util.concurrent package. It contains 2 main methods lock() and unlock(). Wherever we want to ensure that only one thread at a time can access the code, we can acquire the lock by using the lock() method. Once the process is complete, we can release the lock using the unlock() method.

You might be interested in Java Interface

Lock vs Synchronized block

Though a lock and synchronized block are similar, there are few differences as below.

LockSynchronized block
Lock interface contains Lock API lock() and unlock() that we can use in different methodsSynchronized block is present within a method always
Lock supports fairness by specifying the fairness propertyDoes not support fairness
Locks contain tryLock() method that checks if it can acquire the lock()Thread is blocked if it does not have access to synchronized block
Lock contain lockInterrupibilty() method to interrupt a waiting threadWe cannot interrupt a thread that is in waiting state to access the synchronized block

Java Lock interface methods

Below are the methods present in the Lock interface.

MethodDescription
void lock()Acquires the lock
void lockInterruptibly()Acquires the lock until the current thread is interrupted
Condition newCondition()Returns a new condition that is bound to the current lock instance
boolean tryLock()Acquires the lock only if it is free at the specified time
boolean tryLock(long time, TimeUnit unit)Acquires the lock only if it is free at the specified timeperiod until it is not interrupted
void unlock()Releases the lock

Lock implementations

JDK supports multiple implementations for the Lock interface in Java as below:

ReentrantLock

The ReentrantLock class has reentrant characteristics that implement the Lock interface in Java. It acquires a lock using the lock() method and releases the same using the unlock() method. It is important to use the unlock() method within the try-catch block to avoid deadlock conditions. This lock is thread-safe and ensures that only one thread can access the synchronized code at a time.

ReentrantLock l = new ReentrantLock();
int cnt = 0;
  
void counter() {
  l.lock();
    
  try {
    cnt++;
  }
  finally {
    l.unlock();
  }
}

ReentrantReadWriteLock

The ReentrantReadWriteLock is another class that implements the Lock interface in Java. It has a pair of locks for read and write access. This type of lock ensures the safety to read mutable variables as long as no one writes them simultaneously. This means multiple threads can hold the read lock access on the same object provided there is no write access on it. This improves the performance in situations where a read operation is more than a write operation.

It is important to follow the below rule:

ReadLock: Multiple threads can hold read access until there is no thread holding or requesting a write access

WriteLock: Allows only a single thread to hold write access when there is no read/write access on the same object.

The below is a piece of code that demonstrates the working of ReentrantReadWriteLock.

ExecutorService exec = Executors.newFixedThreadPool(2);
    Map<Integer, String> m = new HashMap<Integer,String>();
    ReadWriteLock rwl = new ReentrantReadWriteLock();
    
    exec.submit(() -> {
      rwl.writeLock().lock();
      try {
        Thread.sleep(1000);
        m.put(1,"Java");
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      finally {
        rwl.writeLock().unlock();
      }
    

  });
    
    Runnable read = () -> {
      rwl.readLock().lock();
      
      try {
        System.out.println(m.get(1));
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      finally {
        rwl.readLock().unlock();
      }
    };
    
    exec.submit(read);
    exec.submit(read);

StampedLock

StampedLock is similar to ReentrantReadWriteLock except that the ReadLocks and WriteLocks can contain a timestamp of long type. We can pass this timestamp while using the unlock() method to release the lock. The StampedLock is available from Java 8 onwards. We can also use the timestamp to check if a lock is valid or not.

Java Lock interface example

Below is a simple example that illustrates the working of the Java Lock interface methods lock() and unlock() with multiple threads. Here, we create 3 threads that individually invoke the incCounter() method that increments the counter. We can see that though all 3 threads have started, it executes only one at a time since we have used the Lock interface. When it calls the lock() method, the current thread acquires the lock and increments the counter value and releases it by using the unlock() method. This way it prevents deadlock.

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockDemo {

  public static void main(String[] args) {
    ExecuteTask e = new ExecuteTask();
    Thread t[] = new Thread[3];
    
    for(int i=0;i<3;i++) {
      t[i] = new Thread(new CounterDemo(e), "Thread " + i);
    }
    for(int i=0;i<3;i++) {
      t[i].start();
    }
  
  }
}

class CounterDemo implements Runnable {

  ExecuteTask exec;
  
  public CounterDemo(ExecuteTask exec){
    this.exec = exec;
  }
  
  public void run() {
    System.out.println("Call executeTask to increment counter: " + Thread.currentThread().getName());
    exec.incCounter();
    
  }
  
}

class ExecuteTask {
  int cnt = 0;
  Lock l = new ReentrantLock();
  
  void incCounter() {
    l.lock();
    try {
      cnt++;
    }
    finally {
      System.out.println("Executing " + Thread.currentThread().getName() + ":" + " Counter value: " + cnt);
      l.unlock();
    }
  }
}
Call executeTask to increment counter: Thread 1
Call executeTask to increment counter: Thread 0
Call executeTask to increment counter: Thread 2
Executing Thread 1: Counter value: 1
Executing Thread 0: Counter value: 2
Executing Thread 2: Counter value: 3

You might be interested in Multithreading Interview Questions

Reference