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

0

Всякий раз, когда имеются два потока и два объекта, подлежащих блокированию, возникает опасность возникновения взаимоблокировки (deadlock) – каждый из потоков владеет блокировкой одного объекта и ожидает освобождения другого объекта. Если объект Х обладает synchronzеd -методом, который вызывает synchronzеd-метод объекта У, а У, в свою очередь, также имеет синхронизированный метод, обращающийся к synchronzеd -методу объекта Х, Два потока могут находиться в состоянии ожидания взаимного завершения, чтобы овладеть блокировкой, и ни один из них не окажется способен продолжить работу. Такую тупиковую ситуацию на профессиональном жаргоне называют "смертельным объятием", или клинчем (deadly embrace). Ниже приведен пример класса Friends (друзья), в котором каждый из "друзей", обнимая (hug) другого, пытается избавиться (hugBack) от объятий партнера:

class Friends {

private Friends partner; private String name;

public Friends(String name) {

this.name = name;

}

public synchronized void hug() {

System.out.println(Thread.currentThread().getName()+ " в " + name + ". hug () пытается вызвать " + partner. name + ". hugBack() ") ;

partner.hugBack();

}

private synchronized void hugBack() { system.out.println(Thread.currentThread().getName()+ " в " + name + ". hugBack() ") ;

}

public void becomeFriend(Friends partner) {

this.partner = partner;

}

Рассмотрим следующий сценарий, действующими лицами которого являются jareth и сory, два объекта класса Friends, ставшие "друзьями":

 

1)    поток 1 вызывает synchronzеd -метод jareth.hug; теперь поток 1 владеет блокировкой объекта jareth;

2)    поток 2 вызывает synchronzеd -метод cory. hug; теперь поток 2 владеет блокировкой объекта cory;

 

3)  jareth.hug вызывает synchronzеd -метод cory.hugBack; поток 1 приостанавливает выполнение, переходя в                 стадию ожидания возможности захвата блокировки cory (которой в данный момент владеет поток 2);

 

4) наконец, соrу.hug вызывает synchronzеd -метод jareth. hugBack; поток 2 приостанавливает выполнение, переходя в стадию ожидания возможности захвата блокировки jareth (которой в данный момент владеет поток 1).

 

Имеет место состояние взаимоблокировки: cory не может продолжить работу, пока не будет освобождена блокировка jareth, и наоборот – и два потока попадают в тупиковую ситуацию.

попытаемся реализовать рассмотренный сценарий в виде следующего кода:

public static void main(String[] args) {

final Friends jareth = new Friends("jareth");

 final Friends cory = new Friends("cory");

 jareth.becomeFriend(cory);

cory.becomeFriend(jareth);

new Thread(new Runnable() {

public void run() { jareth.hug(); } }, "потокl"). startO;

new Thread(new Runnable() {

public void run() {

cory.hug(); } }; "поток2"). Start() ;

}

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

 

Поток1 в jareth. hugO пытается вызвать cory. hugBack() Поток2 в cory.hug() пытается вызвать jareth.hugBack()

 

Разумеется, не исключено, что вам повезет, и один из потоков сумеет выполнить код hug целиком еще до момента старта второго потока. Если бы действия, предусмотренные п. 2) и 3), выполнялись В обратном порядке, объект jareth завершил бы выполнение как hug, так и hugBack до того, как объекту cory потребовалась бы блокировка jareth. Но при очередном выполнении программы опасность попадания в тупик все равно остается, поскольку планировщик заданий вправе осуществить другой выбор. Проблема может быть решена несколькими способами. Простейший из них состоит в том, чтобы удалить из объявлений методов hug и hugBack признак synchronized, но синхронизировать оба метода относительно единого объекта которым совместно владеют все объекты Friends. В таком случае может быть выполнена только одна операция hug, что позволяет предотвратить возможность возникновения взаимоблокировки. Более сложные решения в Состоянии разрешить одновременное выполнение нескольких hug и исключить опасность попадания приложения в тупик.

Полную ответственность за появление и предотвращение подобных ситуаций Несет программист. Исполняющая система не может ни выявить, ни предупредить Опасность взаимоблокировок. Отладка кода, чреватого тупиковыми ситуациями, нередко весьма сложна, поэтому следует избегать возникновения проблемы еще на стадии проектирования. Один из общих подходов носит название упорядочения ресурсов (resource ordering). Следуя ему, вы должны присвоить порядковые номера всем блокируемым объектам и затем запрашивать блокировку именно в таком порядке. В этом случае исключается возможность возникновения ситуации, Когда два потока владеют блокировками и пытаются захватить блокировки, принадлежащие "сопернику": оба потока должны запрашивать блокировки в строгом порядке – как только первый поток овладеет первой блокировкой, второй, пытающийся получить в свое распоряжение ту же блокировку, будет заблокирован, и тогда первый поток сможет безопасно приобрести вторую блокировку.

Упражнение 10.8. Поэкспериментируйте с программой Friends. Насколько часто взаимоблокировки возникают в вашей системе на самом деле? Если добавить в текст вызовы метода уiеld, поможет ли это избежать тупиковых Ситуаций? Если возможно, попытайтесь проверить работу программы с помощью не. скольких исполняющих систем. Попробуйте устранить возможность появления взаимоблокировок, не затрагивая признаков синхронизации.

Источник: Арнолд, Кен, Гослинг, Джеймс, Холмс, Дэвид. Язык программирования Java. 3-е изд .. : Пер. с англ. – М. : Издательский дом «Вильяме», 2001. – 624 с. : ил. – Парал. тит. англ.

По теме:

  • Комментарии