Thursday, October 27, 2011

Create and Detect Thread Deadlocks

Just the code:
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
import java.util.Date;
import java.util.Scanner;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Example of how a deadlock might be created, and how to detect it. Two threads
 * are launched, each acquiring the same two resources but in a different order.
 * A delay is introduced in the first thread before acquiring the second
 * resource to set up a race condition such that a deadlock *may* be introduced.
 * The deadlock is not guaranteed to occur; it depends on the length of the
 * delay, which is controlled by the user executing the program. Empirically, on
 * my desktop, I've found that values of 100 sometimes are adequate, while
 * values of 1000+ become increasingly reliable.
 * 
 * Note that looking for deadlocks with the JMX mechanism used here (ThreadMXBean) is
 * described in the javadocs as an operation that "might be expensive". As such,
 * unless you're confident that the cost is not high...I'd recommend not putting
 * this into operation, rather using it in dev/test environment when trying to
 * reproduce a situation observed in production where a deadlock occurred. Note
 * that in production, issuing SIGQUIT (ctl-backslash) will provoke a thread
 * dump (without killing the process), in which information on deadlocked
 * threads will also appear.
 */
public class DeadlockedThreadFinder
{
   // done becomes true when either thread completes; note that if one thread 
   // completes, then the other one will also
   public boolean done;

   /**
    * Launch two threads, each acquiring a lock on two different resources in a
    * different order. The first thread pauses some time T as a function of the
    * given delay.
    *
    * @param delay is the given delay
    */
   public void createDeadlock(final int delay)
   {
      System.out.println("First thread will pause for time T as function of " +
         delay);

      // the resources in contention - if threads use the synchronized keyword, 
      // you could use something like the two Object declared (and commented 
      // out) here; else, use the ReentrantLock if in java.util.concurrent 
      // land.
      //      final Object resource1 = new Object();
      //      final Object resource2 = new Object();
      final Lock lock1 = new ReentrantLock();
      final Lock lock2 = new ReentrantLock();

      // first thread
      Thread t1 = new Thread()
      {
         public void run()
         {
            long id = this.getId();
            lock1.lock(); // alternately use synchronized (resource1) without 
            // the try-finally block
            try
            {
               System.out.println(id + ": obtained first lock");
               // instead of using Thread.sleep(), just create a whole bunch 
               // of expensive objects
               for (int i = 0; i < delay; i++)
               {
                  new Date();
               }

               System.out.println(id + " waiting for 2nd lock...");
               lock2.lock(); // or use synchronized (resource2)
               try
               {
                  System.out.println(id + ": obtained 2nd lock");
               }
               finally
               {
                  lock2.unlock();
               }
               System.out.println(id + " is done!");
               done = true;
            }
            finally
            {
               lock1.unlock();
            }
         }
      };

      // second thread
      Thread t2 = new Thread()
      {
         public void run()
         {
            long id = this.getId();
            lock2.lock(); // alternately use synchronized (resource2) without 
            // the try-finally block
            try
            {
               System.out.println(id + ": obtained 2nd lock");
               System.out.println(id + " waiting for first lock...");
               lock1.lock(); // or use synchronized (resource1)
               try
               {
                  System.out.println(id + ": obtained first lock");
               }
               finally
               {
                  lock1.unlock();
               }
               System.out.println(id + " is done!");
               done = true;
            }
            finally
            {
               lock2.unlock();
            }
         }
      };

      t1.start();
      t2.start();
   }

   /**
    * User must enter an integer that specifies the delay. Values of 10000+ are
    * likely to cause a deadlock. The larger the value entered, the more likely
    * the program will loop a few times detecting no deadlock, and then, once
    * the first thread has satisfied the delay, the program will detect a
    * deadlock until the end of time or user termination of program, whichever
    * comes first.
    */
   public static void main(String[] args) throws Exception
   {
      // the JMX deadlock finder
      ThreadMXBean mxbean = ManagementFactory.getThreadMXBean();

      // user specifies the delay
      System.out.print("Enter delay: ");
      Scanner sc = new Scanner(System.in);

      // launch the threads
      DeadlockedThreadFinder finder = new DeadlockedThreadFinder();
      finder.createDeadlock(sc.nextInt());

      System.out.println("Waiting for either thread to be done...");
      do
      {
         // the JMX deadlock finder will return null if no deadlock detected. 
         // Note that if the threads are using traditional synchronization (i.e. 
         // using the synchronized keyword), then you could also use the 
         // findMonitorDeadlockedThreads method here; but since the threads 
         // might uplevel to use java.util.concurrent, it's advised to instead
         // always use findDeadlockedThreads, which will work for either situation.
         long[] deadlocked = mxbean.findDeadlockedThreads();
         if (deadlocked != null)
         {
            System.out.print("Deadlocked threads: ");
            String comma = "";
            for (long l : deadlocked)
            {
               System.out.print(comma + l);
               comma = ",";
            }
            System.out.println();
         }
         else
         {
            System.out.println("No deadlock detected");
         }
         Thread.sleep(1000);
      }
      while (!finder.done);

      // uh, done
      System.out.println("Done");
   }
}