Обнаружение тупиков в Java - PullRequest
       46

Обнаружение тупиков в Java

62 голосов
/ 20 октября 2008

Давным-давно я сохранил предложение из справочника по Java: "У Java нет механизма для обработки взаимоблокировок. Он даже не узнает, что произошел тупик" (Head First Java 2nd Edition, p.516)

Так что с этим? Есть ли способ поймать тупиковую ситуацию в Java? Я имею в виду, есть ли способ, которым наш код понимает, что произошел случай взаимоблокировки?

Ответы [ 15 ]

78 голосов
/ 20 октября 2008

Начиная с JDK 1.5 в пакете java.lang.management есть очень полезные методы для обнаружения и проверки возникающих взаимоблокировок. См. findMonitorDeadlockedThreads() и findDeadlockedThreads() метод ThreadMXBean класса.

Возможный способ использовать это - иметь отдельный сторожевой поток (или периодическую задачу), который делает это.

Пример кода:

  ThreadMXBean tmx = ManagementFactory.getThreadMXBean();
  long[] ids = tmx.findDeadlockedThreads();
  if (ids != null) {
     ThreadInfo[] infos = tmx.getThreadInfo(ids, true, true);
     System.out.println("The following threads are deadlocked:");
     for (ThreadInfo ti : infos) {
        System.out.println(ti);
     }
  }
18 голосов
/ 20 октября 2008

JConsole может обнаруживать взаимные блокировки в работающем приложении.

11 голосов
/ 20 октября 2008

JDK 5 и 6 будут выгружать информацию о заблокированных блокировках в дампе полного потока (полученного с помощью kill -3, jstack, jconsole и т. Д.). JDK 6 даже содержит информацию о ReentrantLock и ReentrantReadWriteLock. Из этой информации можно диагностировать взаимоблокировку путем нахождения цикла блокировки: поток A удерживает блокировку 1, поток B удерживает блокировку 2, а либо A запрашивает 2, либо B запрашивает 1. По моему опыту это обычно довольно очевидно.

Другие инструменты анализа могут фактически обнаружить потенциальные тупики, даже если они не возникают. Потоковые инструменты от таких поставщиков, как OptimizeIt, JProbe, Coverity и т. Д., Являются хорошими местами для поиска.

8 голосов
/ 02 ноября 2009

Обратите внимание, что существует тип взаимоблокировки с использованием параллельного пакета, который очень трудно отладить. Здесь у вас есть ReentrantReadWriteLock, и один поток захватывает блокировку чтения, а затем (скажем) пытается войти в монитор, удерживаемый другим потоком, который также ожидает захвата блокировки записи. Что делает его особенно трудным для отладки, так это отсутствие записи о том, кто вошел в блокировку чтения. Это просто счет. Поток мог даже выдать исключение и умер, оставив счетчик чтения ненулевым.

Вот пример тупика, который не будет получен упомянутым ранее методом findDeadlockedThreads:

import java.util.concurrent.locks.*;
import java.lang.management.*;

public class LockTest {

    static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public static void main(String[] args) throws Exception {
        Reader reader = new Reader();
        Writer writer = new Writer();
        sleep(10);
        System.out.println("finding deadlocked threads");
        ThreadMXBean tmx = ManagementFactory.getThreadMXBean();
        long[] ids = tmx.findDeadlockedThreads();
        if (ids != null) {
            ThreadInfo[] infos = tmx.getThreadInfo(ids, true, true);
            System.out.println("the following threads are deadlocked:");
            for (ThreadInfo ti : infos) {
                System.out.println(ti);
            }
        }
        System.out.println("finished finding deadlocked threads");
    }

    static void sleep(int seconds) {
        try {
            Thread.currentThread().sleep(seconds*1000);
        } catch (InterruptedException e) {}
    }

    static class Reader implements Runnable {
        Reader() {
            new Thread(this).start();
        }
        public void run() {
            sleep(2);
            System.out.println("reader thread getting lock");
            lock.readLock().lock();
            System.out.println("reader thread got lock");
            synchronized (lock) {
                System.out.println("reader thread inside monitor!");
                lock.readLock().unlock();
            }
        }
    }

    static class Writer implements Runnable {
        Writer() {
            new Thread(this).start();
        }
        public void run() {
            synchronized (lock) {
                sleep(4);
                System.out.println("writer thread getting lock");
                lock.writeLock().lock();
                System.out.println("writer thread got lock!");
            }
        }
    }
}
6 голосов
/ 20 октября 2008

В общем случае Java не предлагает обнаружение взаимоблокировок. Ключевое слово synchronized и встроенные мониторы несколько затрудняют рассуждение о взаимоблокировке, чем в языках с явной блокировкой.

Я бы предложил перейти на использование блокировок java.util.concurrent.Lock и т.п., чтобы облегчить рассуждение о ваших схемах блокировки. На самом деле вы могли бы легко сделать свою собственную реализацию интерфейса блокировки с обнаружением взаимоблокировки. Алгоритм заключается в том, чтобы в основном проходить по графу зависимостей блокировки и искать цикл.

4 голосов
/ 20 октября 2008

Если вы используете Java 5, вы можете вызвать метод findMonitorDeadlockedThreads() в ThreadMXBean, который можно получить с помощью вызова java.lang.management.ManagementFactory.getThreadMXBean(). Это обнаружит взаимоблокировки, вызванные только объектными мониторами. На Java 6 есть findDeadlockedThreads(), который также найдет взаимоблокировки, вызванные "собственными синхронизаторами (например, ReentrandLock и ReentrantReadWriteLock).

Имейте в виду, что эти методы, вероятно, будут дорогостоящими, поэтому их следует использовать только в целях устранения неполадок.

4 голосов
/ 20 октября 2008

Можно избежать взаимных блокировок, если вы следуете простому правилу: все потоки требуют и снимают свои блокировки в том же порядке. Таким образом, вы никогда не попадете в ситуацию, когда может возникнуть тупик.

Даже проблема столовых философов может рассматриваться как нарушение этого правила, поскольку в нем используются относительные понятия левой и правой ложек, которые приводят к разным потокам с использованием разных порядков распределения ложек. Если бы ложки были пронумерованы однозначно, а философы все сначала попытались получить ложку с наименьшим номером, тупик был бы невозможен.

На мой взгляд, профилактика лучше лечения.

Это одно из двух правил, которым я хотел бы следовать, чтобы обеспечить правильную работу потоков. Другой гарантирует, что каждый поток исключительно отвечает за свое собственное выполнение, поскольку он единственный полностью осознает, что он делает в любой момент времени.

Так что это означает, что нет вызовов Thread.stop, используйте глобальный флаг (или очередь сообщений или что-то в этом роде), чтобы сообщить другому потоку, что вы хотите выполнить действие. Затем пусть этот поток выполнит реальную работу.

3 голосов
/ 09 декабря 2011

Java может обнаруживать взаимоблокировки (хотя и не во время выполнения, она все равно может диагностировать и сообщать об этом).

Например, при использовании слегка модифицированной версии кода 'Saurabh M. Chande' ниже (изменили ее на Java и добавили некоторое время для гарантии блокировки при каждом запуске). Как только вы запустите его и он будет заблокирован, если вы наберете:

kill -3 PID   # where 'PID' is the Linux process ID

Он создаст дамп стека, который будет содержать следующую информацию:

Found one Java-level deadlock:
=============================
"Thread-0":
     waiting to lock monitor 0x08081670 (object 0x7f61ddb8, a Deadlock$A),
     which is held by "main"
"main":
      waiting to lock monitor 0x080809f0 (object 0x7f61f3b0, a Deadlock$B),
      which is held by "Thread-0"
3 голосов
/ 20 октября 2008

Dr. Хайнц Кабуц из JavaSpecialists написал интересную и информативную новостную рассылку о взаимоблокировках Java и описывает что-то, называемое ThreadMXBean, в другой проблеме рассылки Между ними вы должны получить представление о проблемах и о том, как правильно создавать свои инструменты.

3 голосов
/ 20 октября 2008

Если вы работаете из командной строки и подозреваете, что зашли в тупик, попробуйте ctrl + break в windows (ctrl + \ в unix), чтобы получить дамп потока. Смотри http://java.sun.com/javase/6/webnotes/trouble/TSG-VM/html/gbmps.html

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...