ReentrantLock: test for deadlocks : Deadlock « Threads « Java





ReentrantLock: test for deadlocks

ReentrantLock: test for deadlocks
 
/*
Java Threads, 3rd Edition
By Scott Oaks, Henry Wong
3rd Edition September 2004 
ISBN: 0-596-00782-5

*/

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

// This is a very slow implementation of a ReentrantLock class and is not for
//   everyday usage. The purpose of this class is to test for deadlocks. The
// lock()
//   method now throws a DeadlockDetectedException, if a deadlock occurs. This
//   Alternate version has some production properties, including faster
//   deadlock check, full implementation of all lock methods, and configurable
//   options.
public class AlternateDeadlockDetectingLock extends ReentrantLock {
  // List of deadlock detecting locks.
  // This array is not thread safe, and must be externally synchronized
  //    by the class lock. Hence, it should only be called by static
  //    methods.
  private static List deadlockLocksRegistry = new ArrayList();

  private static synchronized void registerLock(
      AlternateDeadlockDetectingLock ddl) {
    if (!deadlockLocksRegistry.contains(ddl))
      deadlockLocksRegistry.add(ddl);
  }

  private static synchronized void unregisterLock(
      AlternateDeadlockDetectingLock ddl) {
    if (deadlockLocksRegistry.contains(ddl))
      deadlockLocksRegistry.remove(ddl);
  }

  // List of threads hard waiting for this lock.
  // This array is not thread safe, and must be externally synchronized
  //    by the class lock. Hence, it should only be called by static
  //    methods.
  private List hardwaitingThreads = new ArrayList();

  private static synchronized void markAsHardwait(List l, Thread t) {
    if (!l.contains(t))
      l.add(t);
  }

  private static synchronized void freeIfHardwait(List l, Thread t) {
    if (l.contains(t))
      l.remove(t);
  }

  //
  // Deadlock checking methods
  //
  // Given a thread, return all locks that are already owned
  // Must own class lock prior to calling this method
  private static Iterator getAllLocksOwned(Thread t) {
    AlternateDeadlockDetectingLock current;
    ArrayList results = new ArrayList();

    Iterator itr = deadlockLocksRegistry.iterator();
    while (itr.hasNext()) {
      current = (AlternateDeadlockDetectingLock) itr.next();
      if (current.getOwner() == t)
        results.add(current);
    }
    return results.iterator();
  }

  // Given a lock, return all threads that are hard waiting for the lock
  // Must own class lock prior to calling this method
  private static Iterator getAllThreadsHardwaiting(
      AlternateDeadlockDetectingLock l) {
    return l.hardwaitingThreads.iterator();
  }

  // Check to see if a thread can perform a hard wait on a lock
  // Must call synchronized version only...
  private static boolean canThreadWaitOnLock0(Thread t,
      AlternateDeadlockDetectingLock l) {
    Iterator locksOwned = getAllLocksOwned(t);
    while (locksOwned.hasNext()) {
      AlternateDeadlockDetectingLock current = (AlternateDeadlockDetectingLock) locksOwned
          .next();

      // Thread can't wait if lock is already owned. This is the end
      // condition
      //      for the recursive algorithm -- as the initial condition should be
      //      already tested for.
      if (current == l)
        return false;

      Iterator waitingThreads = getAllThreadsHardwaiting(current);
      while (waitingThreads.hasNext()) {
        Thread otherthread = (Thread) waitingThreads.next();

        // In order for the thread to safely wait on the lock, it can't
        //   own any locks that have waiting threads that already owns
        //   lock. etc. etc. etc. recursively etc.
        if (!canThreadWaitOnLock0(otherthread, l)) {
          return false;
        }
      }
    }
    return true;
  }

  private static synchronized boolean canThreadWaitOnLock(Thread t,
      AlternateDeadlockDetectingLock l) {
    // Skip check if there is no owner
    // There is a race condition is the owner is null. However, it doesn't
    // matter.
    //     Testing for no owner ensures none of the threads in the thread wait
    //     tree will grab it later -- as all locks in the tree are owned.
    if (l.getOwner() == null) {
      return true;
    }
    return canThreadWaitOnLock0(t, l);
  }

  // Options: variable to control behavior
  //    FastFail: if set true, deadlock exception will thrown for every call
  // after
  //              first exception is detected
  //     CleanUp: if set true, lock will cleanup deadlock condition -- allowing
  //              for continued operation after failure. FastFail must be off.
  //    HWSWTime: # of seconds before a Softwait is to be considered as a
  // hardwait.
  //              Default is 60 seconds.
  private static boolean DDLFastFail = false;

  private static boolean DDLCleanUp = false;

  private static int DDLHWSWTime = 60;

  // Core Constructors
  //
  public AlternateDeadlockDetectingLock() {
    this(false, false);
  }

  public AlternateDeadlockDetectingLock(boolean fair) {
    this(fair, false);
  }

  private boolean debugging;

  public AlternateDeadlockDetectingLock(boolean fair, boolean debug) {
    super(fair);
    debugging = debug;
    registerLock(this);
  }

  private static boolean DDLdeadlockDETECTED = false;

  //
  // Core Methods
  //
  public void lock() {
    if (DDLFastFail && DDLdeadlockDETECTED) {
      throw new DeadlockDetectedException("EARILER DEADLOCK DETECTED");
    }

    // Note: Owner can't change if current thread is owner. It is
    //       not guaranteed otherwise. Other owners can change due to
    //       condition variables.
    if (isHeldByCurrentThread()) {
      if (debugging)
        System.out.println("Already Own Lock");
      super.lock();
      freeIfHardwait(hardwaitingThreads, Thread.currentThread());
      return;
    }

    // Note: The wait list must be marked before it is tested because
    //       there is a race condition between lock() method calls.
    markAsHardwait(hardwaitingThreads, Thread.currentThread());
    if (canThreadWaitOnLock(Thread.currentThread(), this)) {
      if (debugging)
        System.out.println("Waiting For Lock");
      super.lock();
      freeIfHardwait(hardwaitingThreads, Thread.currentThread());
      if (debugging)
        System.out.println("Got New Lock");
    } else {
      DDLdeadlockDETECTED = true;
      if (DDLCleanUp)
        freeIfHardwait(hardwaitingThreads, Thread.currentThread());
      throw new DeadlockDetectedException("DEADLOCK DETECTED");
    }
  }

  //
  // Note: It is debatable whether this is a hard or soft wait. Even if
  //       interruption is common, we don't know if the interrupting thread
  //       is also involved in the deadlock. In this alternate version, it
  //       will be treated as a hard wait.
  public void lockInterruptibly() throws InterruptedException {
    if (DDLFastFail && DDLdeadlockDETECTED) {
      throw new DeadlockDetectedException("EARILER DEADLOCK DETECTED");
    }

    // Note: Owner can't change if current thread is owner. It is
    //       not guaranteed otherwise. Other owners can change due to
    //       condition variables.
    if (isHeldByCurrentThread()) {
      if (debugging)
        System.out.println("Already Own Lock");
      try {
        super.lockInterruptibly();
      } finally {
        freeIfHardwait(hardwaitingThreads, Thread.currentThread());
      }
      return;
    }

    // Note: The wait list must be marked before it is tested because
    //       there is a race condition between lock() method calls.
    markAsHardwait(hardwaitingThreads, Thread.currentThread());
    if (canThreadWaitOnLock(Thread.currentThread(), this)) {
      if (debugging)
        System.out.println("Waiting For Lock");
      try {
        super.lockInterruptibly();
      } finally {
        freeIfHardwait(hardwaitingThreads, Thread.currentThread());
      }
      if (debugging)
        System.out.println("Got New Lock");
    } else {
      DDLdeadlockDETECTED = true;
      if (DDLCleanUp)
        freeIfHardwait(hardwaitingThreads, Thread.currentThread());
      throw new DeadlockDetectedException("DEADLOCK DETECTED");
    }
  }

  //
  // Note: It is debatable where is the point between a hard wait and a
  //      soft wait. Is it still a soft wait, if the timeout is large? As
  //      compromise, it is to be considered a hardwait if the timeout
  //      is larger than a specified time. Developers should modify this method
  //      as needed.
  public boolean tryLock(long time, TimeUnit unit)
      throws InterruptedException {
    if (DDLFastFail && DDLdeadlockDETECTED) {
      throw new DeadlockDetectedException("EARILER DEADLOCK DETECTED");
    }

    // Perform operation as a soft wait
    if (unit.toSeconds(time) < DDLHWSWTime) {
      return super.tryLock(time, unit);
    }

    // Note: Owner can't change if current thread is owner. It is
    //       not guaranteed otherwise. Other owners can change due to
    //       condition variables.
    if (isHeldByCurrentThread()) {
      if (debugging)
        System.out.println("Already Own Lock");
      try {
        return super.tryLock(time, unit);
      } finally {
        freeIfHardwait(hardwaitingThreads, Thread.currentThread());
      }
    }

    // Note: The wait list must be marked before it is tested because
    //       there is a race condition between lock() method calls.
    markAsHardwait(hardwaitingThreads, Thread.currentThread());
    if (canThreadWaitOnLock(Thread.currentThread(), this)) {
      if (debugging)
        System.out.println("Waiting For Lock");
      try {
        return super.tryLock(time, unit);
      } finally {
        freeIfHardwait(hardwaitingThreads, Thread.currentThread());
        if (debugging)
          System.out.println("Got New Lock");
      }
    } else {
      DDLdeadlockDETECTED = true;
      if (DDLCleanUp)
        freeIfHardwait(hardwaitingThreads, Thread.currentThread());
      throw new DeadlockDetectedException("DEADLOCK DETECTED");
    }
  }

  // Note 1: Deadlocks are possible with any hard wait -- this includes
  //      the reacquitition of the lock upon return from an await() method.
  //      As such, condition variables will mark for the future hard
  //      wait, prior to releasing the lock.
  // Note 2: There is no need to check for deadlock on this end because
  //      a deadlock can be created whether the condition variable owns the
  //      lock or is reacquiring it. Since we are marking *before* giving
  //      up ownership, the deadlock will be detected on the lock() side
  //      first. It is not possible to create a new deadlock just by releasing
  //      locks.
  public class DeadlockDetectingCondition implements Condition {
    Condition embedded;

    protected DeadlockDetectingCondition(ReentrantLock lock,
        Condition embedded) {
      this.embedded = embedded;
    }

    // Note: The algorithm can detect a deadlock condition if the thead is
    //    either waiting for or already owns the lock, or both. This is why
    //    we have to mark for waiting *before* giving up the lock.
    public void await() throws InterruptedException {
      try {
        markAsHardwait(hardwaitingThreads, Thread.currentThread());
        embedded.await();
      } finally {
        freeIfHardwait(hardwaitingThreads, Thread.currentThread());
      }
    }

    public void awaitUninterruptibly() {
      markAsHardwait(hardwaitingThreads, Thread.currentThread());
      embedded.awaitUninterruptibly();
      freeIfHardwait(hardwaitingThreads, Thread.currentThread());
    }

    public long awaitNanos(long nanosTimeout) throws InterruptedException {
      try {
        markAsHardwait(hardwaitingThreads, Thread.currentThread());
        return embedded.awaitNanos(nanosTimeout);
      } finally {
        freeIfHardwait(hardwaitingThreads, Thread.currentThread());
      }
    }

    public boolean await(long time, TimeUnit unit)
        throws InterruptedException {
      try {
        markAsHardwait(hardwaitingThreads, Thread.currentThread());
        return embedded.await(time, unit);
      } finally {
        freeIfHardwait(hardwaitingThreads, Thread.currentThread());
      }
    }

    public boolean awaitUntil(Date deadline) throws InterruptedException {
      try {
        markAsHardwait(hardwaitingThreads, Thread.currentThread());
        return embedded.awaitUntil(deadline);
      } finally {
        freeIfHardwait(hardwaitingThreads, Thread.currentThread());
      }
    }

    public void signal() {
      embedded.signal();
    }

    public void signalAll() {
      embedded.signalAll();
    }
  }

  // Return a condition variable that support detection of deadlocks
  public Condition newCondition() {
    return new DeadlockDetectingCondition(this, super.newCondition());
  }

  //
  // Testing routines here
  //
  // These are very simple tests -- more tests will have to be written
  private static Lock a = new AlternateDeadlockDetectingLock(false, true);

  private static Lock b = new AlternateDeadlockDetectingLock(false, true);

  private static Lock c = new AlternateDeadlockDetectingLock(false, true);

  private static Condition wa = a.newCondition();

  private static Condition wb = b.newCondition();

  private static Condition wc = c.newCondition();

  private static void delaySeconds(int seconds) {
    try {
      Thread.sleep(seconds * 1000);
    } catch (InterruptedException ex) {
    }
  }

  private static void awaitSeconds(Condition c, int seconds) {
    try {
      c.await(seconds, TimeUnit.SECONDS);
    } catch (InterruptedException ex) {
    }
  }

  private static void testOne() {
    new Thread(new Runnable() {
      public void run() {
        System.out.println("thread one grab a");
        a.lock();
        delaySeconds(2);
        System.out.println("thread one grab b");
        b.lock();
        delaySeconds(2);
        a.unlock();
        b.unlock();
      }
    }).start();

    new Thread(new Runnable() {
      public void run() {
        System.out.println("thread two grab b");
        b.lock();
        delaySeconds(2);
        System.out.println("thread two grab a");
        a.lock();
        delaySeconds(2);
        a.unlock();
        b.unlock();
      }
    }).start();
  }

  private static void testTwo() {
    new Thread(new Runnable() {
      public void run() {
        System.out.println("thread one grab a");
        a.lock();
        delaySeconds(2);
        System.out.println("thread one grab b");
        b.lock();
        delaySeconds(10);
        a.unlock();
        b.unlock();
      }
    }).start();

    new Thread(new Runnable() {
      public void run() {
        System.out.println("thread two grab b");
        b.lock();
        delaySeconds(2);
        System.out.println("thread two grab c");
        c.lock();
        delaySeconds(10);
        b.unlock();
        c.unlock();
      }
    }).start();

    new Thread(new Runnable() {
      public void run() {
        System.out.println("thread three grab c");
        c.lock();
        delaySeconds(4);
        System.out.println("thread three grab a");
        a.lock();
        delaySeconds(10);
        c.unlock();
        a.unlock();
      }
    }).start();
  }

  private static void testThree() {
    new Thread(new Runnable() {
      public void run() {
        System.out.println("thread one grab b");
        b.lock();
        System.out.println("thread one grab a");
        a.lock();
        delaySeconds(2);
        System.out.println("thread one waits on b");
        awaitSeconds(wb, 10);
        a.unlock();
        b.unlock();
      }
    }).start();

    new Thread(new Runnable() {
      public void run() {
        delaySeconds(1);
        System.out.println("thread two grab b");
        b.lock();
        System.out.println("thread two grab a");
        a.lock();
        delaySeconds(10);
        b.unlock();
        c.unlock();
      }
    }).start();

  }

  public static void main(String args[]) {
    int test = 1;
    if (args.length > 0)
      test = Integer.parseInt(args[0]);
    switch (test) {
    case 1:
      testOne(); // 2 threads deadlocking on grabbing 2 locks
      break;
    case 2:
      testTwo(); // 3 threads deadlocking on grabbing 2 out of 3 locks
      break;
    case 3:
      testThree(); // 2 threads deadlocking on 2 locks with CV wait
      break;
    default:
      System.err.println("usage: java DeadlockDetectingLock [ test# ]");
    }
    delaySeconds(60);
    System.out.println("--- End Program ---");
    System.exit(0);
  }
}class DeadlockDetectedException extends RuntimeException {

    public DeadlockDetectedException(String s) {
        super(s);
    }
}



           
         
  










Related examples in the same category

1.Demonstrates how deadlock can be hidden in a program
2.Another deadlock demoAnother deadlock demo
3.Deadlock DetectingDeadlock Detecting
4.Using interrupt() to break out of a blocked thread.Using interrupt() to break out of a blocked thread.
5.Deadlock Detector
6.DeadLock Detector 2