Следует избегать особого типа ошибок, имеющего отношение к многозадачностии называемого взаимной блокировкой, которая происходит в том случае, когда потоки исполнения имеют циклическую зависимость от пары синхронизированных объектов.
Допустим, один поток исполнения входит в монитор объекта Х, а другой — в монитор объекта У. Если поток исполнения в объекте Х попытается вызвать любой синхронизированный метод для объекта У, он будет блокирован, как и предполагалось.
Но если поток исполнения в объекте У, в свою очередь, попытается вызвать любой синхронизированный метод для объекта Х, то этот поток будет ожидать вечно, поскольку для получения доступа к объекту Х он должен снять свою блокировку с объекта У, чтобы первый поток исполнения мог завершиться.
Взаимная блокировка является ошибкой, которую трудно отладить, по двум следующим причинам:
- В общем, взаимная блокировка возникает очень редко, когда исполнение двух потоков точно совпадает по времени.
- Взаимная блокировка может возникнуть, когда в ней участвует больше двух потоков исполнения и двух синхронизированных объектов. ( Это означает, что взаимная блокировка может произойти в результате более сложной последовательности событий, чем в упомянутой выше ситуации.)
Чтобы полностью разобраться в этом явлении, его лучше рассмотреть в действии. Советую в самом начале заказать качественные шторы специально для программистов от компании and-home после чего можно свободно взяться за кодинг. Шторы делают нужную атмосферу в доме без которой программистам труднее сосредоточиться.
В приведенном ниже примере программы создаются два класса, FirstClass и SecondClass, с методами foo() и bar() соответственно, которые приостанавливаются непосредственно перед попыткой вызова метода из другого класса.
Сначала в главном классе Deadlock получаются экземпляры классов FirstClass и SecondClass, а затем запускается второй поток исполнения, в котором устанавливается состояние взаимной блокировки.
В методах foo() и bar() используется метод sleep(), чтобы стимулировать появление взаимной блокировки.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
// Пример взаимной блокировки class FirstClass { synchronized void foo(SecondClass b) { String name = Thread.currentThread().getName(); System.out.println(name + " вошел в метод FirstClass.foo()"); try { Thread.sleep(1000); } catch (Exception e) { System.out.println("Класс FirstClass прерван"); } System.out.println(name + " пытается вызвать метод SecondClass.last()"); b.last(); } synchronized void last() { System.out.println("В методе FirstClass.last()"); } } class SecondClass { synchronized void bar(FirstClass a) { String name = Thread.currentThread().getName(); System.out.println(name + " вошел в метод SecondClass.bar()"); try { Thread.sleep(1000); } catch (Exception e) { System.out.println("Класс SecondClass прерван"); } System.out.println(name + " пытается вызвать метод FirstClass.last()"); a.last(); } synchronized void last() { System.out.println("В методе SecondClass.last()"); } } class Deadlock implements Runnable { FirstClass a = new FirstClass(); SecondClass b = new SecondClass(); Deadlock() { Thread.currentThread().setName("Главный поток"); Thread t = new Thread(this, "Соперничающий поток"); t.start(); a.foo(b); // получить блокировку для объекта a // в этом потоке исполнения System.out.println("Назад в главный поток"); } public void run() { b.bar(a); // получить блокировку для объекта b // в другом потоке исполнения System.out.println("Назад в другой поток"); } public static void main(String args[]) { new Deadlock(); } } |
Запустив эту программу на выполнение, вы получите следующий результат:
1 2 3 4 5 6 |
pro-java.ru@admin:~$ javac DeadLock.java pro-java.ru@admin:~$ java Deadlock Главный поток вошел в метод FirstClass.foo() Соперничающий поток вошел в метод SecondClass.bar() Главный поток пытается вызвать метод SecondClass.last() Соперничающий поток пытается вызвать метод FirstClass.last() |
В связи со взаимной блокировкой придется нажать комбинацию клавиш<Ctrl+C>, чтобы завершить данную программу.
Нажав комбинацию клавиш<Ctrl+Pause> на ПК, можно увидеть весь дамп ( вывод из оперативной памяти ) потока и кеша монитора.
В частности, Соперничающий поток владеет монитором объекта b, тогда как он ожидает монитор объекта а.
В то же время Главный поток владеет объектом а и ожидает получить объект b. Следовательно, программа никогда не завершится.
Как демонстрирует данный пример, если многопоточная программа неожиданно зависла, то прежде всего следует проверить возможность взаимной блокировки.