Tuesday 7 June 2016

ReentrantLock in Java

ReentrantLocks

Lets me first try to explain reentrancy concept in a simplistic and generic way. We will come to Java specific details a bit later. Reentrancy is lay man terms means ability to enter again. In terms of thread it mean thread can acquired same lock again without blocking itself. Refreshing our multi threading concepts here. When you synchronize over an object the thread obtains a lock on it before entering the critical region (inside synchronized block) and till this thread releases this lock no other thread can acquire it and enter the critical region. 

NOTE : We do this to make compound operations atomic so that there is no race condition or invalid state.

But what happens when we call an synchronized instance method from inside another synchronized instance method. Eg - 


public class TestClass {

    public synchronized void method1() {
        // some code
        method2();
    }

    public synchronized void method2() {
        // some other code
    }

}

Here for a thread to enter either of the method has to obtain a lock on the instance (this) before entering the method. Now we are calling method 2 from method1 which is again synchronized with same instance (this). So thread will try to acquire lock again. If locks were not reentrant in nature we would have ended up in deadlock. 

Note : In Java all intrinsic locks are reentrant in nature. 

Note : Synchronization is built around an internal entity known as the intrinsic lock or monitor lock. (The API specification often refers to this entity simply as a "monitor.") Intrinsic locks play a role in both aspects of synchronization: enforcing exclusive access to an object's state and establishing happens-before relationships that are essential to visibility.Every object has an intrinsic lock associated with it. Explicit locks are introduced in Java 1.5 like semaphore, cyclic barrier etc.

Now lets see Reentant lock in Java that was introduced in Java 1.5.

ReentrantLock  in Java

As per Java doc

A reentrant mutual exclusion Lock with the same basic behavior and semantics as the implicit monitor lock accessed using synchronized methods and statements, but with extended capabilities like -
  •  It takes a fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation. Note however, that fairness of locks does not guarantee fairness of thread scheduling. Thus, one of many threads using a fair lock may obtain it multiple times in succession while other active threads are not progressing and not currently holding the lock. Also note that the untimed tryLock method does not honor the fairness setting. It will succeed if the lock is available even if other threads are waiting - ReentrantLock(boolean fair)
  • It provides tryLock() method which acquires lock only if it is not held by other threads. We can also use timeout with this method which means thread will time out of waiting if lock is not acquired till the timeout value. This is better than intrinsic locks where you have to wait indefinitely.
  • It also provides facility to interrupt thread while waiting using.  ReentrantLock provides a method called lockInterruptibly() [Acquires the lock unless the current thread is interrupted.], which can be used to interrupt thread when it is waiting for lock.
  • Lastly it also provides functionality to get list of all threads waiting for the lock - getWaitingThreads(Condition condition)
    (Returns a collection containing those threads that may be waiting on the given condition associated with this lock).

NOTE : This lock supports a maximum of 2147483647 recursive locks by the same thread. Attempts to exceed this limit result in Error throws from locking methods.

Example -


 class Test {
   ReentrantLock reLock = new ReentrantLock();
   // ...

   public void m() {
     reLock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       reLock.unlock()
     }
   }
 }


Working

Also, the way reentrancy is achieved is by maintaining a counter for number of locks acquired and owner of the lock. If the count is 0 and no owner is associated to it, means lock is not held by any thread. When a thread acquires the lock, JVM records the owner and sets the counter to 0.If same thread tries to acquire the lock again the counter is incremented, and when the owning thread exist synchronized block counter is decremented. When count reaches 0 again lock is released.


Most generic example are Segments used in ConcurrenHashMap. Each segment is essentially a ReentrantLock that allows only single thread to access that part of the map. You can refer to the link above to see how it works. Adding relevant snippet here -

static final class Segment<K,V> extends ReentrantLock implements Serializable {

    //The number of elements in this segment's region.
    transient volatile int count;
    //The per-segment table. 
    transient volatile HashEntry<K,V>[] table;
}

V put(K key, int hash, V value, boolean onlyIfAbsent) {
    lock();
    try {
        //logic to store data in map
    } finally {
        unlock();
    }
}


NOTE : ReentrantLock was introduced since Java 5.

Related Links

t> UA-39527780-1 back to top