Отчет о DeadLock'ах в работающем приложении

    Привет тебе хабражитель!
    В данной статье, точнее примере я хочу показать как можно получить информацию о залоченных потоках Вашего многопоточного java приложения. Под катом будет представлен пример простейшей ситтуации DeadLock'а, а также способы получения информации о нем.

    Простейшая ситтуация получения DeadLoack'а — когда один поток пытается залочить уже залоченный другим потоком ресурс.
    1. public class TestDeadLockTracker {
    2.   public static void main(String[] args) {
    3.     final Object lock1 = new Object();
    4.     final Object lock2 = new Object();
    5.     Thread t1 = new Thread("Deadlock Test T1") {
    6.       public void run() {
    7.         synchronized (lock1) {
    8.           try {
    9.             Thread.sleep(150L);
    10.           } catch (Throwable ignored) {
    11.           }
    12.           synchronized (lock2) {
    13.             try {
    14.               Thread.sleep(30L);
    15.             } catch (Throwable ignored) {
    16.             }
    17.             lock2.notify();
    18.           }
    19.           lock1.notify();
    20.         }
    21.       }
    22.     };
    23.     Thread t2 = new Thread("Deadlock Test T2") {
    24.       public void run() {
    25.         synchronized (lock2) {
    26.           try {
    27.             Thread.sleep(150L);
    28.           } catch (Throwable ignored) {
    29.           }
    30.           synchronized (lock1) {
    31.             try {
    32.               Thread.sleep(300L);
    33.             } catch (Throwable ignored) {
    34.             }
    35.             lock1.notify();
    36.           }
    37.           lock2.notify();
    38.         }
    39.       }
    40.     };
    41.     t1.start();
    42.     t2.start();
    43.   }
    44. }
    * This source code was highlighted with Source Code Highlighter.

    В данном примере оба потока будут залочены намертво между собой по ресурсам lock1 и lock2.
    Выхода из программы не произойдет никогда. Начиная с версии java 1.5 очень просто можно отлавливать такие ситтуации, для этого существует специализированный бин для взамодействия с подсистемой потоков ThreadMXBean. Javadoc достаточно подробный так что читайте.
    Давайте напишем простейший метод для получения отчета о наших проблемах с локировками.
    1. public static String reportDeadlocks(ArrayList<Long> knownDeadlocks) {
    2.   long[] threads = threadMXBean.findDeadlockedThreads();
    3.  
    4.   if ((threads == null) || (threads.length == 0)) {
    5.     return null;
    6.   }
    7.  
    8.   final ArrayList<Long> dumpIDs = new ArrayList<Long>(threads.length);
    9.   for (long thread : threads) {
    10.     boolean found = false;
    11.  
    12.     if (knownDeadlocks != null) {
    13.       Iterator itr = knownDeadlocks.iterator();
    14.       while ((!found) && (itr.hasNext())) {
    15.         found = thread == ((Number) itr.next()).longValue();
    16.       }
    17.     }
    18.  
    19.     if (found) continue;
    20.     dumpIDs.add(thread);
    21.   }
    22.  
    23.   if (dumpIDs.size() == 0) return null;
    24.  
    25.   if (knownDeadlocks != null) knownDeadlocks.addAll(dumpIDs);
    26.  
    27.   ThreadDumpFilter filter = new ThreadDumpFilter() {
    28.     public boolean include(Thread t) {
    29.       long id = t.getId();
    30.       for (Long dumpID : dumpIDs) {
    31.         if (dumpID == id) return true;
    32.       }
    33.       return false;
    34.     }
    35.  
    36.     public String toString() {
    37.       return String.format("Deadlocked threads: %s", dumpIDs);
    38.     }
    39.   };
    40.   StringWriter writer = new StringWriter();
    41.   ThreadDump.print(new PrintWriter(writer), filter);
    42.   writer.flush();
    43.  
    44.   return String.format("Found %d new deadlocked threads (%d total deadlocked):\n%s",
    45.       dumpIDs.size(), threads.length, writer.toString());
    46. }
    * This source code was highlighted with Source Code Highlighter.

    В переменной threadMXBean лежит инстанс бина ThreadMXBean. На второй строчке кода мы получаем список идентификаторов тредов у которых есть проблемы с локировками. После чего используем вспомогательный класс ThreadDump(есть в исходниках) для вывода информации о наших проблемных потоках. Теперь нам необходим отдельный поток для сбора информации о проблемах в нашем приложении. Код его очень простой:
    1. private static class DeadlockTrackerThread extends Thread {
    2.   private volatile boolean checking = true;
    3.  
    4.   DeadlockTrackerThread() {
    5.     super("Deadlock Tracking Thread");
    6.     setPriority(1);
    7.     setDaemon(true);
    8.   }
    9.  
    10.   public void setChecking(boolean b) {
    11.     this.checking = b;
    12.     if (this.checking) return;
    13.     interrupt();
    14.   }
    15.  
    16.   public void run() {
    17.     ArrayList<Long> knownDeadlocks = new ArrayList<Long>();
    18.     try {
    19.       while (this.checking) {
    20.         String report = ThreadUtils.reportDeadlocks(knownDeadlocks);
    21.         if (report != null) {
    22.           System.out.print("Dedlock detected " + report);
    23.         }
    24.         try {
    25.           Thread.sleep(6000L);
    26.         } catch (InterruptedException intx) {}
    27.       }
    28.     } catch (Exception e) {
    29.       System.err.print("Error: " + e.getMessage());
    30.     }
    31.   }
    32. }
    * This source code was highlighted with Source Code Highlighter.

    Он проверяет наличие отчета о проблемах каждые 6 секунд, и если такой присутствует то выводит его в System.out.

    Теперь добавим пару строчек кода в нашу первую программу, для проверки что эта куча кода работает:
    1. public class TestDeadLockTracker {
    2.   public static void main(String[] args) {
    3.     if (!ThreadUtils.isDeadlockDetectionSupported()) {
    4.       System.out.println("DeadlockDetection not supported");
    5.       return;
    6.     }
    7.     ThreadUtils.setDeadlockDetectionEnabled(true);
    8.     ThreadUtils.setTrackingDeadlocksEnabled(true);
    9.     final Object lock1 = new Object();
    10.     ....
    11.   }
    12. }
    * This source code was highlighted with Source Code Highlighter.

    Запускаем и, в консоле нашей программы мы увидим примерно следующие строки:
    Dedlock detected Found 2 new deadlocked threads (2 total deadlocked):
    — Thread Dump Deadlocked threads: [11, 10]
    Thread[Deadlock Test T1,5,main] «Deadlock Test T1» Id=10 BLOCKED on java.lang.Object@272d7a10 owned by «Deadlock Test T2» Id=11

    TestDeadLockTracker$1.run(TestDeadLockTracker.java:27)
    Thread[Deadlock Test T2,5,main] «Deadlock Test T2» Id=11 BLOCKED on java.lang.Object@1aa8c488 owned by «Deadlock Test T1» Id=10

    TestDeadLockTracker$2.run(TestDeadLockTracker.java:45)
    — End Thread Dump

    Исходники
    Спасибо за внимание.
    Поделиться публикацией
    Комментарии 10
      0
      перенеси в блог Java
        0
        Класс, спасибо!
          +1
          Метод findDeadlockedThreads появился в java 1.6, так что лучше или использовать findMonitorDeadlockedThreads, или предварительно определить наличие нужного метода с помощью reflection, и делать вызов через Method.invoke(). Благо, возвращают оба этих метода long[].

          Кстати, старая статья на ту же тему: Automatically Detecting Thread Deadlocks
            +2
            А ещё можно без всяких выкрутасов нажать в консоли Ctrl+Break.
              +1
              или использовать утилиту jstack
              0
              Правильно ли я понял что дедлок показывает только когда он реально случился? Что в этом может быть такого интересного? Применение видится только что на систему автоматических тестов навесить проверку чтобы работа службы прерывался раньше таймаута, если дедлок обнаружен.
                0
                такая система может пригодиться к примеру в пулов коннекций, потоков и т.д. к примеру существует такой пул потоков к базе данных под названием c3p0. К примеру есть конечное число коннекций к базе данных, и какая-нибудь коннекция подвисла или jdbc не отдает ресурс, в c3p0 есть поток, который мониторит dead lock's и в случае возникновения, прибивает потоки.
                  0
                  И посылает разгневанное письмо разработчикам :) Вообще забавный подход к разработке получается, пиши дедлочный код, прибивай потоки. Только я не понял почему на стороне базы данных c3p0 предлагается мониторить дедлогки, это же надо делать на стороне приложения, не?
                    0
                    c3p0 — не база, а библиотека пула соединений. Как apache commons dbcp. И работает она, соответственно, на стороне приложения.
                0
                Кстати, и notify() в данном коде совсем без надобности.

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое