Мертвые замки в Java: когда они происходят?

Я работаю над приложением для J2ME, и когда-то он полностью замораживается, и для завершения AMS требуется некоторое время. Мне кажется, что это проблема с блокировкой.

Не могли бы вы посоветовать мне, что может вызвать мертвые блокировки? Например, вызов синхронного метода объекта вызывает мертвую блокировку, если он вызывает другой собственный синхронизированный метод?

Спасибо!


Обновление

Я правильно говорю, что тупик должен произойти в следующем случае:

Объект P вызывает синхронизирующий метод объекта A, который вызывает синхронизирующий метод объекта B, который вызывает синхронизирующий метод объекта А

Извините, если это выглядит глупо со мной, скорее всего. Но вот почему я спрашиваю. Спасибо!

Ответ 1

Будет ли, например, вызов синхронизированного метода объекта вызвать мертвую блокировку, если он вызывает другой собственный синхронизированный метод?

Нет, поскольку блокировки synchronized в Java реентерабельны: вы можете получить одну и ту же блокировку из одного потока несколько раз без проблем.

Замыкает тупик, например. когда нить A удерживает блокировку L и пытается получить блокировку M, в то время как нить B удерживает блокировку M и пытается получить блокировку L. Таким образом, оба потока ожидают блокировки, удерживаемой другой, и не могут прогрессировать, чтобы освободить свою собственную блокировку. Это заставляет обе потоки ждать вечно. Ситуация может включать более двух потоков.

Тупики могут быть очень трудными для обнаружения, поэтому типичным способом является попытка избежать их путем тщательного проектирования. Самое простое средство для достижения этого - обеспечить, чтобы любой поток, который должен был приобретать несколько блокировок, приобретает их всегда в одном и том же предопределенном глобальном порядке. Например. если в приведенном выше примере оба потока A и B сначала попытаются получить блокировку L, затем заблокируйте M, не будет тупика.

Ваша проблема может быть вызвана другими вещами, кроме тупика, например, livelock (когда поток, хотя и не заблокирован, все еще не может прогрессировать, потому что он продолжает повторять операцию, которая всегда терпит неудачу).

Обновление: доступ к блокировке извне объекта

С встроенными блокировками Java (т.е. synchronized) основной объект Lock сам по себе не отображается в коде, а только объект, который мы блокируем. Рассмотрим

class MyClass {
  private Object object = new Object();

  public synchronized void synchronizedOnThis1() {
    ...
  }
  public void synchronizedOnThis2() {
    synchronized(this) {
      ...
    }
  }
  public void synchronizedOnPrivateObject() {
    synchronized(object) {
      ...
    }
  }
}

class ExternalParty {
  public void messUpLocks() {
    final MyClass myObject = new MyClass();
    synchronized(myObject) {
      Thread otherThread = new Thread() {
        public void run() {
            myObject.synchronizedOnThis1();
        }
      };
      otherThread.start();
      // do a lengthy calculation - this will block the other thread
    }
  }
}

Оба метода synchronizedOnThis* синхронизируются с содержимым MyClass; синхронизация этих двух методов эквивалентна. Однако экземпляр класса, очевидно, доступен для внешнего мира, поэтому внешняя сторона может использовать его как блокировку вне класса, как показано выше. И если объект доступен из другого потока, и этот поток вызывает один из его методов synchronizedOnThis*, этот вызов будет блокироваться, пока этот поток находится внутри блока synchronized(myObject).

OTOH метод synchronizedOnPrivateObject использует закрытый объект как блокировку. Если этот объект каким-либо образом не публикуется внешним сторонам, никто не может (случайно или злонамеренно) вызвать тупик, связанный с этой блокировкой.

Ответ 2

Наиболее вероятной причиной может быть два потока, пытающихся получить блокировки для двух объектов. Thread 1 блокирует A и ждет B, но поток 2 блокирует B и ждет A. Оба потока в конечном итоге ожидают объекты, которые никогда не будут выпущены.

Существует не быстрое решение, за исключением того, что ваш код делает вещи в очень определенном порядке. Как правило, синхронизированный блок должен быть как можно меньше, обычно на конкретном объекте, чем на методе. Положите много регистрации и посмотрите, можете ли вы понять, происходит ли что-то подобное.

Java 5 имеет явные объекты Lock, которые позволяют выполнять более мелкий контроль, включая таймауты по сравнению с простыми синхронизированными блоками, но я не знаю, будут ли они полезны для J2ME. Существует backport из Java 5 concurrency libs, с которым можно будет работать с J2ME - http://backport-jsr166.sourceforge.net/, если эта проблема достаточно больших, чтобы заслужить их использование.

Ответ 4

, когда происходит тупик?

Чрезмерное использование синхронизации с непоследовательным упорядочением блокировки вызывает тупик.

Решение во избежание блокировки блокировки

Поддерживайте порядок и используйте меньше синхронизации.

Ниже приведен один сценарий, который делает тупик.

    public void method1() {
            synchronized (String.class) {
                System.out.println("Aquired lock on String.class object");

                synchronized (Integer.class) {
                    System.out.println("Aquired lock on Integer.class object");
                }
            }
        }

        public void method2() {
            synchronized (Integer.class) {
                System.out.println("Aquired lock on Integer.class object");

                synchronized (String.class) {
                    System.out.println("Aquired lock on String.class object");
                }
            }
        }

Если методы method1() и method2() оба будут вызваны двумя или несколькими потоками, существует хорошая вероятность взаимоблокировки, потому что если thead 1 блокирует объект Sting при выполнении метода1(), а поток 2 получает блокировку объекта Integer в то время как при выполнении метода2() оба будут ждать друг друга, чтобы освободить блокировку на Integer и String, чтобы продолжить, что никогда не произойдет.

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

public void method1() {
    synchronized (Integer.class) {
        System.out.println("Aquired lock on Integer.class object");

        synchronized (String.class) {
            System.out.println("Aquired lock on String.class object");
        }
    }
}

public void method2() {
    synchronized (Integer.class) {
        System.out.println("Aquired lock on Integer.class object");

        synchronized (String.class) {
            System.out.println("Aquired lock on String.class object");
        }
    }
}

Теперь не было бы тупика, потому что оба метода обращаются к блокировке объекта Integer и String в том же порядке. поэтому, если поток A получает блокировку объекта Integer, поток B не будет продолжаться до тех пор, пока поток A не освободит блокировку Integer, так же, как поток A не будет заблокирован, даже если поток B удерживает блокировку String, потому что теперь поток B не ожидает, что поток A будет освобожден. Блокировка целых чисел продолжить дальше.

Предоставлено