Взаимная блокировка

Особый тип ошибок, которого следует избегать, имеющий отношение к многозадачности — это взаимная блокировка (deadlock), которая происходит, когда потоки имеют циклическую зависимость от пары синхронизированных объектов. Например, предположим, что один поток входит в монитор объекта X, а другой — в монитор объекта Y. Если поток в X попытается вызвать любой синхронизированный метод Y, он заблокируется, как и ожидалось. Однако если поток Y, в свою очередь, попытается вызвать любой синхронизированный метод X, то поток будет ожидать вечно, потому что для получения доступа к X он должен снять свой собственный блок на Y, чтобы первый поток мог отработать. Взаимная блокировка является ошибкой, которую трудно отладить, по двум описанным ниже причинам.

  • В общем, она случается довольно редко, когда выполнение двух потоков точно совпадает по времени.
  • Она может происходить, когда в этом участвует более двух потоков и двух синхронизированных объектов. (То есть взаимная блокировка может случиться в результате более сложной последовательности событий, чем в приведенном примере.)

Чтобы полностью разобраться с этим явлением, лучше рассмотреть его в действии. Следующий пример создает два класса — А и в, с методами f оо () и bar () соответственно, которые приостанавливаются непосредственно перед попыткой вызова метода другого класса. Главный класс, названный Deadlock, создает экземпляры А и В, затем запускает второй поток, устанавливающий состояние взаимной блокировки. Методы f оо () и bar () используют sleep (), чтобы стимулировать появление взаимной блокировки.

// Пример взаимной блокировки.
class А {
synchronized void foo(B b) {
String name = Thread.currentThread().getNameO;
System.out.println(name + " вошел в A.foo");
try {
Thread.sleep(1000);
}
catch(Exception e) {
System.out.println("А прерван");
}
System.out.println(name + " пытается вызвать B.lastO");
b.lastO ;
}
synchronized void last() {
System.out.println("внутри A.last");
}
}
class В {
synchronized void bar(A a) {
String name = Thread.currentThread().getNameO;
System.out.println(name + " вошел в В.bar");
try {
Thread.sleep(1000);
}
catch(Exception e) {
System.out.println("В прерван");
}
System.out.println(name + " пытается вызвать A.lastO");
a.last();
}
synchronized void last() {
System.out.println("внутри A.last");
}
}
class Deadlock implements Runnable { A a = new A () ;
В b = new В();
Deadlock () {
Thread.currentThread().setName("MainThread");
Thread t = new Threadfthis, "RacingThread");
t.start () ;
a.foo(b); // получить блокировку внутри этого потока.
System.out.println("Назад в главный поток");
}
public void run() {
b.bar(a); // получить блокировку b в другом потоке.
System.out.println ("Назад в другой поток");
}
public static void main(String args[]) {
new Deadlock ();
}
}

Когда вы запустите эту программы, то увидите следующий результат:

MainThread вошел в A.foo
RacingThread вошел в В.bar
MainThread пытается вызвать В.last ()
RacingThread пытается вызвать A.last ()

Поскольку эта программа заблокирована, вам придется нажать для завершения программы. Вы можете видеть весь поток и дамп кэша монитора, нажав . Вы увидите, что RacingThread владеет монитором на Ь, в то время как последний ожидает монитора на а. В то же время MainThread владеет а и ожидает Ь. Эта программа никогда не завершится. Как иллюстрирует этот пример, если ваша многопоточная программа неожиданно зависла, то первое, что вы должны проверить — возможность взаимной блокировки.