Locks In Java — Part 1 [ Lock Interface]

Avinashsoni
7 min readNov 7, 2023

--

In this Article we are going to see the internal of Lock Interface in Java.

Lock is a tool in java to control access to a common resource when we have a multithreaded scenario for example booking sites where we are trying to book seats concurrently our seat gets locked when we start the booking process.

It allows the Flexiblity of Releasing Locks In Different order As Compared to the Synchronized Block giving the user the flexiblity of implementation

Lets see the Methods Under the Lock Interface in Java and Deep Dive Into them one by one , we will also see a Interesting Condition Interface which basically allow the thread to perform a condition check after it acquire the access to lock.

Methods Under Lock Interface :

void lock()

this methods help us to acquire the lock although if the lock is not acquired then the thread which is running currently becomes disabled for any further schedulling untill it acquire the lock .

remember lock is a resource and it may or may not be available as some other thread might have the access to the shared resource currently.

void unlock()

as the name suggest it help us to release the lock .

lets see some scenarios where we can see how we apply lock :

Note : ReentrantLock are the locks which allow the same thread to acquire the same lock more than one times [ it will be taken up in a seperate article ]


public class LockTest {

private static Lock lock = new ReentrantLock();

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

Thread thread1 = new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() +
" trying to acquire the lock");
lock.lock();
System.out.println(Thread.currentThread().getName() +
" has acquired lock");
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() +
" released the lock");
}

});


Thread thread2 = new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() +
" trying to acquire the lock");
lock.lock();
System.out.println(Thread.currentThread().getName() +
" has acquired lock");

} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() +
" released the lock");
}

});


thread1.start();
thread2.start();


}
}

Output :

We can see that the both threads try to get the lock then thread 1 acquires the lock and then release it and finally it get acquired by the Thread 0 .

Lets take example of Movie Booking Sites Like BookMyShow

public class BookMyShow {
private final Lock lock = new ReentrantLock();
private boolean [] seats;

public BookMyShow(int total){
seats = new boolean[total];
for(int i = 0 ; i < total ; ++i) seats[i] = true;

}

public void bookSeat(String customerName, int seatNo){
if(seatNo < 0 || seatNo > seats.length)
throw new RuntimeException("invalid booking request");

lock.lock();
try {
if(seats[seatNo]){
seats[seatNo] = false;
System.out.println(customerName +
" has booked the seat No " + seatNo);
}
else {
System.out.println("Sorry " + customerName +
" Seat is already Booked !");
}
}finally {
lock.unlock();
}

}

public static void main(String[] args) {
BookMyShow bookMyShow = new BookMyShow(10);
Thread customer1 = new Thread(()-> {
bookMyShow.bookSeat("Avinash",2);
});
Thread customer2 = new Thread(() -> {
bookMyShow.bookSeat("Aditya",2);
});

customer1.start();
customer2.start();
}
}

here only one customer will be able to book when both try to book simualtenously

boolean tryLock() :

it helps us to allow to avoid scenarios where some other thread try to acquire lock and fails as lock is already acquired , saving us unnecessary operations to do this acquisition check as it return true only if the lock is available for the thread to get acquired.

Some Advantages of using TryLock() Method :

  • Non — Blocking : using this we dont block the current running thread so in case we don’t get the access to the Lock then we can continue the code further
  • Thread Contention : Help us to avoid Scenarios Where more than one Thread are trying to get access to the lock and thus avoiding starvation

Lets See by example of the BookMyShow :

now with tryLock only one customer even reaches to the try block so it help us to avoid blocking scenarios

public class BookMyShow {
private final Lock lock = new ReentrantLock();
private boolean [] seats;

public BookMyShow(int total){
seats = new boolean[total];
for(int i = 0 ; i < total ; ++i) seats[i] = true;

}

public void bookSeat(String customerName, int seatNo){
if(seatNo < 0 || seatNo > seats.length)
throw new RuntimeException("invalid booking request");

if(lock.tryLock()){
lock.lock();
try {
if(seats[seatNo]){
seats[seatNo] = false;
System.out.println(customerName + " has booked the seat No " + seatNo);
}
else {
System.out.println("Sorry " + customerName + " Seat is already Booked !");
}
}finally {
lock.unlock();
}
}
else {
System.out.println(customerName + " Some one else is booking it!");
}


}

public static void main(String[] args) {
BookMyShow bookMyShow = new BookMyShow(10);
Thread customer1 = new Thread(()-> {
bookMyShow.bookSeat("Avinash",2);
});
Thread customer2 = new Thread(() -> {
bookMyShow.bookSeat("Aditya",2);
});

customer1.start();
customer2.start();
}
}

output :

tryLock() with Timeout :

it basically help us to wait for a given time only to see if the lock is available or not else it returns false.

void lockInterruptibly():

it basically allow the waiting thread to get interrupt and throw a interrupted exception to handle it gracefully.

public class LockInterfaceTest {

private final static Lock lock = new ReentrantLock();
public static void main(String[] args) {

Thread thread1 = new Thread(() -> {
try {
System.out.println("Thread 1 is trying to
acquire the lock !!");
lock.lockInterruptibly();
System.out.println("Thread 1 has acquired the Lock.");
Thread.sleep(1000);
}
catch (InterruptedException e){
System.out.println("Thread 1 was Interrupted !!");
}
finally {
lock.unlock();
}
});

Thread thread2 = new Thread(() -> {
try {
System.out.println("Thread 2 is trying
is acquire the lock !!");
lock.lockInterruptibly();
System.out.println("Thread 2 has acquired the lock");
}
catch (InterruptedException e){
System.out.println("Thread 2 was interrupted while
waiting for the lock!");

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


thread1.start();
thread2.start();

try {
Thread.sleep(1000);
}
catch (InterruptedException e){
}


thread1.interrupt();

try {
thread1.join();
thread2.join();
}catch (InterruptedException e){
e.printStackTrace();
}



}
}

output :

Condition newCondition():

Condition is used when we want to perform a check before the thread runs it’s task after it has acquired the Lock .

Lets see the Methods Under the Condition Interface to Get an Idea about how it works :

  • void await() : it allow the thread to give its lock to some other thread and wait untill this thread is signalled by some other thread .
package com.example.demo.executor;

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

public class ConditionTest {
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();

private int shared_val = 0 ;

public void waitForVal(int expected){
System.out.println(Thread.currentThread().getName() + " acquired the lock");
lock.lock();
try {

while (shared_val != expected) {
try {
condition.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println("Condition got fullfilled now");
}finally {
System.out.println(Thread.currentThread().getName() + " released the lock");
lock.unlock();
}
}

public void updateVal(int newVal){
System.out.println(Thread.currentThread().getName() + " acquired the lock for update");
System.out.println(Thread.currentThread().getName() + " trying to update the value as " + newVal);
lock.lock();
try {
shared_val = newVal;
condition.signal();
}finally {
System.out.println(Thread.currentThread().getName() + " released the lock after update");
lock.unlock();
}
}
public static void main(String[] args) {
ConditionTest test = new ConditionTest();
Thread waiting = new Thread(()-> test.waitForVal(5));
Thread updating = new Thread(() -> test.updateVal(4));
Thread updating2 = new Thread(() -> test.updateVal(5));

waiting.start();
updating.start();
updating2.start();
}
}

basically when we call the await method the lock associated with the thread get released and the thread becomes disabled for any further schedulling untill one of the below events occurs :

-> Some other thread calls the signal method and the current thread is choosen to be awaken

-> SignallAll is called

-> Thread gets interrupted

  • awaitUninterruptibly() : same cases as above only the thread gets interrupted condition is ruled out.
  • awaitNanos(long timeout) : makes the thread wait untill this timeout elapses
  • similarly there are other methods to handle time based / date based scenarios
  • signal() : it wakes up one waiting thread , just one additional things to note if a thread who has lost the lock upon being called the await method earlier is selected to be awaken by signal method called by other thread then thread must re — acquire the lock before awakening
  • signalAll() : awakens all the threads

this sums up the Lock Interface in Java , In next Article we will see about Read-Write locks in java and see how they work internally.

Thanks For Reading !!

--

--

Avinashsoni

SDE@BNY MELLON | Spring Boot | Learning and Growing EveryDay 😁 Linkedln 👇: https://www.linkedin.com/in/asoni93/