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

0

Если вы имеете дело с двумя потоками и с двумя блокируемыми объектами, может возникнуть ситуация взаимной блокировки (deadlock), при которой каждый объект дожидается снятия блокировки с другого объекта. Представим себе, что объект X содержит синхронизированный метод, внутри которого вызывается синхронизированный метод объекта Y, который, в свою очередь, также содержит синхронизированный метод для вызова синхронизированного метода объекта X. Каждый объект ждет, пока с другого объекта не будет снята блокировка, и в результате ни один из них не работает. Подобная ситуация иногда называется “смертельными объятиями” (deadly embrace). Рассмотрим сценарий, в соответствии с которым объекты jareth и cory относятся к некоторому классу Friend ly:

1. Поток 1 вызывает синхронизированный метод jareth.hug. С этого момента поток 1

осуществляет блокировку объекта jareth.

2. Поток 2 вызывает синхронизированный метод cory.hug. С этого момента поток 2

осуществляет блокировку объекта cory.

3. Теперь cory.hug вызывает синхронизированный метод jareth.hugBack. Поток 1 блокируется, поскольку он ожидает снятия блокировки с cory (в настоящее время осуществляемой  потоком 2).

4. Наконец, jareth.hug вызывает синхронизированный метод cory.hugBack. Поток 2 также блокируется, поскольку он ожидает снятия блокировки с jareth (в настоящее время осуществляемой  потоком 1).

Возникает взаимная блокировка — cory не работает, пока не снята блокировка с jareth, и наоборот, и два потока навечно застряли в тупике.

Конечно, вам может повезти, и один из потоков завершит весь метод hug без участия второго. Если бы этапы 3 и 4 следовали бы в другом порядке, то объект jareth выполнил бы hug и hugBack еще до того, как cory понадобилось бы заблокировать jareth. Однако в будущих запусках того же приложения планировщик потоков мог бы сработать иначе, приводя к взаимной блокировке. Самое простое решение заключается в том, чтобы объявить методы hug и hugBack несинхронизированными и синхронизировать  их работу по одному объекту, совместно используемому всеми объектами Friendly. Это означает, что в любой момент времени во всех потоках может выполняться ровно один метод hug — опасность взаимной блокировки при этом исчезает. Благодаря другим, более хитроумным приемам удается одновременно выполнять несколько hug без опасности взаимной блокировки.

Вся ответственность в вопросе взаимной блокировки возлагается на вас. Java не умеет ни обнаруживать такую ситуацию, ни предотвращать ее. Подобные проблемы с трудом поддаются отладке, так что предотвращать их надо на стадии проектирования.  В разделе “Библиография” приведен ряд полезных ссылок на книги, посвященные вопросу проектирования потоков и решению проблем блокировки.

9.7. Приостановка потоков

Поток может быть приостановлен (suspended), если необходимо быть уверенным в том,

что он возобновится лишь с вашего разрешения. Для примера допустим, что пользователь нажал кнопку C ANCEL во время выполнения длительной операции. Работу следует приостановить до того момента, когда пользователь подтвердит (или нет) свое решение. Фрагмент программы может выглядеть следующим образом:

Thread spinner;   //поток, выполняющий обработку

public void userHitCancel() {

spinner.suspend();              // приостановка

if (askYesNo("Really Cancel?"))

spinner.stop();            // прекращение операции

else

}

spinner.resume();          // передумал!

Метод userHitCancel сначала вызывает suspend для потока, выполняющего операцию, чтобы остановить его вплоть до вашего распоряжения. Затем пользователь должен ответить, действительно ли он хочет отменить операцию. Если да, то метод stop снимает поток; в противном случае метод resume возобновляет работу потока.

Приостановка ранее остановленного потока, а также возобновление работы потока,

который не был приостановлен, не приводит ни к каким нежелательным последствиям.

9.8. Прерывание потока

В некоторых методах класса Thread упоминается прерывание (interrupting) потока. Соответствующие  методы зарезервированы  для возможности, которая вскоре будет включена в Java. На момент написания этой книги они еще не полностью реализованы; попытка их вызова приводит к возбуждению исключения NoSuchMethodError  и уничтожению вызывающего потока. Вполне возможно, что к тому моменту, когда вы будете читать эту книгу, эти методы уже будут реализованы. В данном разделе приводится их краткий обзор.

Концепция “прерывания” оказывается полезной, если выполняемому потоку необходимо предоставить некоторую степень контроля над моментом обработки события. Например, в цикле вывода может понадобиться информация из базы данных, извлекаемая

посредством транзакции; если при этом поступает запрос на прекращение работы,

желательно дождаться нормального завершения транзакции. Поток пользовательского интерфейса может реализовать такой запрос, прерывая поток вывода и давая ему возможность дождаться конца транзакции. Подобная схема будет хорошо работать лишь в том случае, если поток вывода “хорошо себя ведет” и в конце каждой транзакции проверяет, не поступил ли запрос на прерывание (и прекращает работу в этом случае).

Прерывание потока в общем случае не должно влиять на его работу, однако некоторые методы (такие, как sleep или wait) возбуждают исключение InterruptedException. Если в вашем потоке во время прерывания выполнялся один из таких методов, то будет возбуждено прерывание Interrupted Exception.

Для работы с прерываниями используются несколько методов. Метод interrupt посылает прерывание в поток; метод isInterrupted проверяет факт прерывания потока; статический метод interrupted проверяет, прерывался ли текущий поток.

Источник: Арнольд К., Гослинг Д. – Язык программирования Java (1997)

По теме:

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