Pages

Friday 7 February 2014

Reentrant Lock in Java


- ReentrantLock in Java is added on java.util.concurrent package in Java 1.5 along with other concurrent utilities.
- ReentrantLock implements Lock, providing the same mutual exclusion and memory visibility guarantees as synchronized.
- Acquiring a ReentrantLock has the same memory semantics as entering a synchronized block, and releasing a ReentrantLock has the same memory semantics as exiting a synchronized block.
- And like synchronized,ReentrantLock offers reentrant locking semantics i.e. it allows a thread to recursively acquire the same lock that it is holding.

Extended capabilities include:

  • Non block structured Locking:  With intrinsic locks, acquire release pairs are block structured. A lock is always released in the same basic block in which it  was  acquired,  regardless  of  how  control  exits  the  block. However in reentrant lock you can acquire the lock in one method and release it in some other method.
  • The ability to lock interruptibly: The lockInterruptibly method allows you to try to acquire a lock while  remaining responsive to interruption. lockInterruptibly() may block if the lock is already held by another thread and will wait until the lock is acquired. This is the same as with regular lock(). But if another thread interrupts the waiting thread lockInterruptibly() will throw InterruptedException.
  • The ability to have more than one condition variable per monitor. Monitors that use the synchronized keyword can only have one. This means reentrant locks support more than one wait()/notify() queue.
  • Fairness: The constructor for this class accepts an optional 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. Synchronized blocks are unfair.
  • Polled and Timed Lock Acquisition: ReentrantLock provides convenient tryLock() method, which acquires lock only if its available or not held by any other thread. An overloaded method of tryLock takes the time it should wait and try acquiring the lock before exiting.

 Examples:

  1. Guarding Object State Using ReentrantLock. 

    void lockTest() {
     Lock lock = new ReentrantLock();
     lock.lock();
     try {
      // update object state
      // catch exceptions and restore invariants if necessary
     } finally {
      lock.unlock();
     }
    }
    

    This code is somewhat more complicated than using intrinsic locks. The lock must be released in a finally block. Otherwise, the lock would never be released if the guarded code were to throw an exception.

  2. Polled and Timed Lock acquisition

    package com.concurrency;
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class ReentrantLockingDemo {
     final Lock lock = new ReentrantLock();
    
     public static void main(final String... args) {
      new ReentrantLockingDemo().go();
     }
    
     private void go() {
      new Thread(newRunable(), "Thread1").start();
      new Thread(newRunable(), "Thread2").start();
     }
    
     private Runnable newRunable() {
      return new Runnable() {
    
       @Override
       public void run() {
        do {
         try {
    
          if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
           try {
    
            System.out.println("locked thread " + Thread.currentThread().getName());
            Thread.sleep(1000);
           } finally {
            lock.unlock();
            System.out.println("unlocked locked thread " + Thread.currentThread().getName());
           }
           break;
          } else {
           System.out.println("unable to lock thread " + Thread.currentThread().getName() + " will re try again");
          }
         } catch (InterruptedException e) {
          e.printStackTrace();
         }
        } while (true);
       }
      };
     }
    }
    

    Output:
    locked thread Thread1
    unable to lock thread Thread2 will re try again
    unlocked locked thread Thread1
    locked thread Thread2
    unlocked locked thread Thread2

  3. Interruptible Lock Acquisition.

    package com.concurrency;
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class ReentrantLockInterruptiblyDemo {
     private Lock lock = new ReentrantLock();
     public ReentrantLockInterruptiblyDemo() {
      lock.lock();
     }
     
      public void f() {
       try {
        // This will never be available to a second task
        lock.lockInterruptibly(); // Special call
        System.out.println("lock acquired in f()");
       } catch(InterruptedException e) {
        System.out.println("Interrupted from lock acquisition in f()");
       }
      }
      public static void main(String[] args) throws Exception {
      Thread t = new Thread(new Blocked2());
      t.start();
      TimeUnit.SECONDS.sleep(1);
      System.out.println("Issuing t.interrupt()");
      t.interrupt();
     }
    }
    
    class Blocked2 implements Runnable {
     ReentrantLockInterruptiblyDemo blocked = new ReentrantLockInterruptiblyDemo();
     public void run() {
      System.out.println("Waiting for f() in BlockedMutex");
      blocked.f();
      System.out.println("Broken out of blocked call");
     }
    }
     
    

    Output:
    Waiting for f() in BlockedMutex
    Issuing t.interrupt()
    Interrupted from lock acquisition in f()
    Broken out of blocked call

  4. Using conditions in ReentrantLock.

    This Example is taken from http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/locks/Condition.html

    We have a bounded buffer which supports put and take methods. If a take is attempted on an empty buffer, then the thread will block until an item becomes available; if a put is attempted on a full buffer, then the thread will block until a space becomes available. We would like to keep waiting put threads and take threads in separate wait-sets so that we can use the optimization of only notifying a single thread at a time when items or spaces become available in the buffer. This can be achieved using two Condition instances.

    public class BoundedBuffer {
     final Lock lock = new ReentrantLock();
     final Condition notFull = lock.newCondition();
     final Condition notEmpty = lock.newCondition();
    
     final Object[] items = new Object[100];
     int putptr, takeptr, count;
    
     public void put(Object x) throws InterruptedException {
      lock.lock();
      try {
       while (count == items.length)
        notFull.await();
       items[putptr] = x;
       if (++putptr == items.length)
        putptr = 0;
       ++count;
       notEmpty.signal();
      } finally {
       lock.unlock();
      }
     }
    
     public Object take() throws InterruptedException {
      lock.lock();
      try {
       while (count == 0)
        notEmpty.await();
       Object x = items[takeptr];
       if (++takeptr == items.length)
        takeptr = 0;
       --count;
       notFull.signal();
       return x;
      } finally {
       lock.unlock();
      }
     }
    }
    





No comments:

Post a Comment