Custom ReentrantLock implementation - implement Lock interface and an use case discussion

In previous post, we discussed about Pros and Cons of Lock (java.util.concurrent.locks) over synchronized methods and statements and concluded that Lock interface provides high programmatic control and some times efficient too.The main agenda of this post is to create a custom ReentrantLock class by implementing Lock interface and use that custom class to show case an ticket booking use case where lock is acquired and released using lock() and unlock() method respectively.
Java doc of Lock interface discusses about three forms of lock acquisition (interruptible, non-interruptible, and timed), however for sake of simplicity we assume that no thread will be interrupted and we will just focus on syntax and semantics of lock() and unlock() method. Lets create a custom lock interface consisting of three methods as follows:-
/*
 * For sake of simplicity - assume custom lock interface having only three
 * methods.
 */
interface CustomLock {
 public void lock();
 public boolean tryLock();
 public void unlock();
}

lock() method sample code:- 

/*lock method implementation of CustomLock interface*/
public synchronized void lock() {
 /*
  * Acquires the lock if it is not held by another thread and set lock
  * hold count to 1.
  */
 if (lockHoldCount == 0) {
  lockHoldCount++;
  threadId = Thread.currentThread().getId();
 }
 /*
  * If current thread already holds lock then hold count is increased by
  * 1 - Chain locking.
  */
 else if (lockHoldCount > 0
   && threadId == Thread.currentThread().getId()) {
  lockHoldCount++;
 }
 // If the lock is held by another thread then the current
 // thread waits for another thread to release lock.
 else {
  try {
   wait();
   lockHoldCount++;
   threadId = Thread.currentThread().getId();
  } catch (InterruptedException e) {
   e.printStackTrace();
  }
 }
}
Explanation:- lockHoldCount is a instance variable of class implementing lock interface. If lock() method is called from context of any thread and lockHoldCount value is 0 then lockHoldCount is incremented and if lockHoldCount value is non-zero and lock has been already acquired by the calling thread then hold count is incremented(chain locking). Otherwise, thread has to wait until other treads executes notify() method and releases lock.

unlock() method sample code:- 

/*unlock method is executed to release lock*/
public synchronized void unlock() {
 /*
  * If current thread is not holding the lock, if unlock is called it
  * throws IllegalMonitorStateException.
  */
 if (lockHoldCount == 0)
  throw new IllegalMonitorStateException();
 /* If lock is held, decrement lock hold count by 1 */
 lockHoldCount--;

 /*
  * If lockHoldCount is 0, lock is released and waiting thread is
  * notified.
  */
 if (lockHoldCount == 0)
  notify();
}
Explanation:- Decrement lockHoldCount if it is greater than zero, else throw exception IllegalMonitorStateException (It indicates, lock was not acquired and thread is trying to release it). 

trylock() method sample code:-

/*tryLock method returns true if lock is available and calls lock() interanlly*/
public synchronized boolean tryLock() {
 /*
  * Acquires the lock if it is not held by another thread and // returns
  * true
  */
 if (lockHoldCount == 0) {
  lock();
  return true;
 }
 // If lock is held by another thread then method return false.
 else
  return false;
}

Concrete customReentrantLock class implementing CustomLock interface:-

Create a class file name it as CustomReentrantLock.java and copy/paste following sample code lines.
/**
 * http://www.devinline.com
 */
package com.devinline.locking;

/*
 * For sake of simplicity - assume custom lock interface having only three
 * methods.
 */
interface CustomLock {
 public void lock();

 public boolean tryLock();

 public void unlock();
}

public class CustomReentrantLock implements CustomLock {
 /* Maintain number of locks acquired by a thread */
 int lockHoldCount;

 /* Id of thread which is currently holding the lock. */
 long threadId;

 /**
  * Creates an instance of CustomReentrantLock and Initial lock hold count is
  * 0.
  */
 CustomReentrantLock() {
  lockHoldCount = 0;
 }

 @Override
 public synchronized void lock() {
  /*
   * Acquires the lock if it is not held by another thread and set lock
   * hold count to 1.
   */
  if (lockHoldCount == 0) {
   lockHoldCount++;
   threadId = Thread.currentThread().getId();
  }
  /*
   * If current thread already holds lock then hold count is increased by
   * 1 - Chain locking.
   */
  else if (lockHoldCount > 0
    && threadId == Thread.currentThread().getId()) {
   lockHoldCount++;
  }
  // If the lock is held by another thread then the current
  // thread waits for another thread to release lock.
  else {
   try {
    wait();
    lockHoldCount++;
    threadId = Thread.currentThread().getId();
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  }
 }

 @Override
 public synchronized void unlock() {
  /*
   * If current thread is not holding the lock, if unlock is called it
   * throws IllegalMonitorStateException.
   */
  if (lockHoldCount == 0)
   throw new IllegalMonitorStateException();
  /* If lock is held, decrement lock hold count by 1 */
  lockHoldCount--;

  /*
   * If lockHoldCount is 0, lock is released and waiting thread is
   * notified.
   */
  if (lockHoldCount == 0)
   notify();

 }

 @Override
 public synchronized boolean tryLock() {
  /*
   * Acquires the lock if it is not held by another thread and // returns
   * true
   */
  if (lockHoldCount == 0) {
   lock();
   return true;
  }
  // If lock is held by another thread then method return false.
  else
   return false;
 }
}

Ticket booking use case implementation - use lock()/unlock() of CustomReentrantLock  

The main goal of this use case is to use CustomReentrantLock class for acquiring and releasing lock while multiple threads are trying for some shared scarce resources. Below is the driver program which creates 5 threads(customer for ticket booking) and these 5 threads will try to acquire lock and book a ticket, out of 5 only 3 threads will be able to book ticket successfully(Since only 3 tickets are available).
package com.devinline.locking;

public class TicketBookingDriverProgram {

 public static void main(String[] args) {
  CustomLock customLock = new CustomReentrantLock();
  TicketBookingIRCTC myRunnable = new TicketBookingIRCTC(customLock, 3);
  new Thread(myRunnable, "Passanger-1 Nikhil").start();
  new Thread(myRunnable, "Passanger-2 Ranjan").start();
  new Thread(myRunnable, "Passanger-3 Yunnus").start();
  new Thread(myRunnable, "Passanger-4 CKM").start();
  new Thread(myRunnable, "Passanger-5 Ritz").start();
 }
}

class TicketBookingIRCTC implements Runnable {

 int ticketsAvailable; // scarce resource
 CustomLock customLock;

 public TicketBookingIRCTC(CustomLock customLock, int totalTicket) {
  this.customLock = customLock;
  ticketsAvailable = totalTicket;
 }

 public void run() {

  System.out.println("Waiting to book ticket : "
    + Thread.currentThread().getName());
  /* get hold of lock for booking ticket */
  customLock.lock();

  if (ticketsAvailable > 0) {
   System.out.println("Ticket booking started  for : "
     + Thread.currentThread().getName());

   // Ticket booking time is 2 sec, so sleep for 2sec
   try {
    Thread.sleep(1000);
   } catch (Exception e) {
   }
   /* Update available ticket count */
   ticketsAvailable--;
   System.out.println("Congratulation!!, Ticket BOOKED "
     + "successfully for : " + Thread.currentThread().getName());
   System.out.println("currently ticketsAvailable = "
     + ticketsAvailable);
  } else {
   ticketsAvailable--;
   System.out.println("Sorry!! Ticket NOT BOOKED for : "
     + Thread.currentThread().getName()
     + ". Current booking status is Waiting list(W/L): "
     + Math.abs(ticketsAvailable));
  }
  customLock.unlock();
 }
}
=====================Sample output====================
Waiting to book ticket : Passanger-1 Nikhil
Ticket booking started  for : Passanger-1 Nikhil
Waiting to book ticket : Passanger-2 Ranjan
Waiting to book ticket : Passanger-5 Ritz
Waiting to book ticket : Passanger-3 Yunnus
Waiting to book ticket : Passanger-4 CKM
Congratulation!!, Ticket BOOKED successfully for : Passanger-1 Nikhil
currently ticketsAvailable = 2
Ticket booking started  for : Passanger-2 Ranjan
Congratulation!!, Ticket BOOKED successfully for : Passanger-2 Ranjan
currently ticketsAvailable = 1
Ticket booking started  for : Passanger-5 Ritz
Congratulation!!, Ticket BOOKED successfully for : Passanger-5 Ritz
currently ticketsAvailable = 0
Sorry!! Ticket NOT BOOKED for : Passanger-3 Yunnus. Current booking status is Waiting list(W/L): 1
Sorry!! Ticket NOT BOOKED for : Passanger-4 CKM. Current booking status is Waiting list(W/L): 2
=====================================================
Explanation:- The constructor of class TicketBookingIRCTC initializes ticketsAvailable to 3 and main threads spawn five threads for booking three tickets, depending on OS which thread scheduled first - calls lock method and acquire lock, book ticket and decrement ticketsAvailable count.
When ticketsAvailable count becomes zero, else block is executed and no ticket is booked for other two threads(as displayed ticket in waiting list)

2 Comments

Previous Post Next Post