Locks In Java [Part-5.1]: Reentrant Locks In Java — Sync Class

Avinashsoni
6 min readMar 11, 2024

--

In this Article we will discuss about Reentrant Locks In Java.

Reentrant Lock is allowed to be acquired again by the same thread.

so the same thread can re enter the critical section multiple times(limit for recursive locks by same thread is -> 2147483647). it can be used in scenarios where we have a recursive function which is calling itself again and again and thread needs to enter the criticial section to perform a given operation.

Use Case

Consider a Scenario where we are doing a Banking Transaction and we need to verify the account balance multiple times for the transaction to complete in this scenario as there can be concurrent threads doing the same operation we will need some lock which can allow the thread to enter again unlike synchronized thread where the thread will have to wait if it is already performing some operation for fetching the balance and it needs to re enter the critical section. Please Note : This Doesn’t Means that more than one thread can enter the critical section at a given time. only one thread can re enter multiple times the critical section using this . ( if we have used synchronized block then for the same thread to reenter we cant do it and have to wait untill the same thread running inside the critical section completes the operation).

Lets now deep dive into how the reentrant lock works internally and what other classes play roles in doing the locking and synchronization operation in the reentrant locks.

Sync

reentrant lock use the Sync class which is static class inside the reentrant lock class.

Sync Class in turn extends the Abstract Queued Synchronizer class which is a framework for creating lock and synchronizer in java.

Sync Provide us the base for doing the lock / interruptlock / unlock , etc operation in the reentrant locks.

Now this class has these following methods which we will discuss one by one.

  1. tryLock()
  2. lock()
  3. lockInterruptibly()
  4. tryLockNanos()
  5. tryRelease()
  6. isHeldExcusively()
  7. getOwner()
  8. getHoldCount()
  9. isLocked()

tryLock()

tryLock method is used to determine if we can take a lock or not. this help us to avoid any errors which occurs when lock is held by some other thread and we try to acquire the lock

this returns a Boolean value stating if we can take the lock or not

lets see the implementation and details about it within the code:

        final boolean tryLock() {
// Find the Current Running thread on which this is called
Thread current = Thread.currentThread();
// to get the state [ 0 -> unacquired lock > 0 -> acquired ]
// state determine the count of lock being acquired
int c = getState();
// if state is zero then we can acquire the lock so we do a CAS
// we first compare if the value is 0 then we set it to 1 [state]
// if the value is zero then we set the ExclusiveOwner thread as the
// current thread

if (c == 0) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (getExclusiveOwnerThread() == current) {
// if the exclusive thread is the currrent thread then
// we increment the state hold count and see if overflow has
// happened
if (++c < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(c);
return true;
}
// if there is some other scenario then we return false as the lock which is
// in the critical section currently is different than the one calling this
// method
return false;
}

getState() is a function in the AQS(AbstractQueuedSynchronizer) which help us to determine the current synchronization state.

compareAndSet() : it performs the atomic operation of setting the state with the second parameter if it is successfully compare with the first value.

getExclusiveOwnerThread() : it is a function from Abstract Ownable Synchronizer ( AQS extends this interface along with serializable)

see more about AOS -> https://medium.com/@avinashsoni9829/locks-in-java-part-4-abstract-ownable-synchronizers-1e4033f66d17

lock()

lock method uses the initialTryLock() : this method is used to check if we are allowed to acquire the lock before moving towards the AQS methods.

it is a abstract method defined in the reentrant lock class.

implementation:

         final void lock() {
if (!initialTryLock())
acquire(1);
}

acquire is a method in the Abstract Queued Synchronizer

this method internally uses a acquire method in the AQS class which has the logic to do the Acquire

this method accepts

Params:
node — null unless a reacquiring Condition

arg — the acquire argument

shared — true if shared mode else exclusive

interruptible — if abort and return negative on interrupt

timed — if true use timed waits

time — if timed, the System.nanoTime value to timeout

we pass acquire(null, arg, false, false, false, 0L)

so we are trying to acquire in exclusive mode with no timewaits and interrupt allowed durring acquire [ rest the implementation interally uses a very complex logic and is beyond the usage in daily life ]

lockInterruptibly()

         final void lockInterruptibly() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!initialTryLock())
acquireInterruptibly(1);
}

if interruption has occurred then we throw the exception

otherwise we check for the initialTryLock and then we call the acquireInterruptibly method with 1

here again we call the acquire method with the values

acquire(null, arg, false, true, false, 0L) < 0)

here see we have kept the interruptible flag as true rest it is same as the Lock Method acquire call.

tryLockNanos()

final boolean tryLockNanos(long nanos) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return initialTryLock() || tryAcquireNanos(1, nanos);
}

if the thread is interrupted then we throw the exception rest we try the initialLock method and the tryAcquireNanos function in the AQS which again internally calls the acquire method we are using previously.

here we can see that our interruptible can be true and also we are passing nanos then we need to pass those values as well.

acquire(null, arg, false, true, true, System.nanoTime() + nanosTimeout)

we pass the interruptible and timed flag as true and also pass the time value

here is the implementation of this method in the AQS

if (!Thread.interrupted()) {
if (tryAcquire(arg))
return true;
if (nanosTimeout <= 0L)
return false;
int stat = acquire(null, arg, false, true, true,
System.nanoTime() + nanosTimeout);
if (stat > 0)
return true;
if (stat == 0)
return false;
}

we can see how the method is handling different scenarios [ again such level of knowledge is not much helpful and used for very severe cases so we are skipping the details and just getting idea what actually happens at a overall level ]

tryRelease()

this method is used for testing if release can be done ( opposite of tryAcquire())

implementation:

        protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (getExclusiveOwnerThread() != Thread.currentThread())
throw new IllegalMonitorStateException();
boolean free = (c == 0);
if (free)
setExclusiveOwnerThread(null);
setState(c);
return free;
}

lets see what is happening in this method

  • we are getting the state and decrementing the releases we want
  • also we first check if the exclusiveOwner thread is not the current thread ( otherwise we are handling some other thread and we need to throw exception)
  • next we check after release if the state is 0 or not
  • if zero we set the exclusive thread to null ( so this means there is no exclusive thread acquring the lock now so it is free)

isHeldExclusively()

this simply checks if the exclusive thread from (Abstract Ownable Synchronizer) is the current thread or not.

getOwner()

we check if the state is equal to 0 then we send null otherwise send the exclusiveOwner Thread

getHoldCount()

this check if the thread is Held Exclusively ( that is one thread only can re enter at a time) then it send the state otheriwse it returns 0.

isLocked()

from the starting we know if the state of the lock is 1 ( or greater ) then it is acquired so

for not acquired it should be 0

so we just simply check if the lock state is not zero ( !=0) then it is locked otherwise free.

in this article we covered one part of the reentrant lock and saw details about the sync class , this class is extended by two other classes fairSync and NonFairSync to give us the fairnessOperator logic which we will take up in the next part.

thanks for reading!! 😁

--

--

Avinashsoni

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