Что такое взаимная блокировка deadlock
Перейти к содержимому

Что такое взаимная блокировка deadlock

  • автор:

Два простых правила для предотвращения взаимных блокировок на мьютексах

Так сложилось, что это третий пост в блоге нашей компании, и, как и первые два, он посвящен вопросам многопоточного программирования и проблемам, которые при этом возникают. Получилось так неслучайно, ведь мы на собственной «шкуре» испытали, что ситуации, возникающие при написании многопоточных программ, невероятно сложны для отладки, так как во многом определяются динамикой работы программы на конкретной аппаратной платформе. Уверен, что большинство программистов сталкивались с ситуацией, когда программа, которая прекрасно работает на одном компьютере, на другом совершенно неожиданно начинает дедлочиться практически «на ровном месте».

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

Рецепты, описанные в этом посте весьма просты, но далеко не все даже профессиональные программисты используют осознанно, руководствуясь скорее ощущениями «почему-то кажется, что не стоит захватывать тот мьютекс из под захваченного этого». Так жили и мы долгие-долгие годы, когда в один ужасно ответственный момент не обнаружили, что на «железке», которую нужно срочно отправить заказчику, наш любимый софт не может и часа прожить без дедлока. Убив на решение этой задачи несколько дней работы своих ведущих программистов, мы приняли решение, которое изменило нашу жизнь – мы занялись суровой формализацией ситуаций взаимных блокировок, включающей строгие математические доказательства того, почему так можно делать, а так нельзя. Надо сказать, что исследование наше выродилось в кандидатскую диссертацию одного из сотрудников, но я не уверен, что использованный там формат изложения будет интересен здесь…

Итак, о блокировках с самого начала…

Природа взаимных блокировок

Не будем вдаваться в сложные математически выверенные определения ситуаций взаимных блокировок и скажем просто: взаимная блокировка – это такое состояние системы, в котором один поток ожидает наступления чего-то, а это что-то не может произойти потому, что другой поток ожидает наступления чего-то от первого потока.

Традиционно принято считать, что причиной блокировок всегда являются мьютексы (mutex), однако это не совсем точно. Причиной блокировок могут являться любые средства и механизмы синхронизации, которые предполагают ожидание чего-либо одного потока со стороны другого, например, ожидание сигнала на переменной кондиции (condition variable) или, что значительно менее очевидно, ожидание завершение другого потока (wait/join thread). В теории, на самом деле, второй случай является тем же самым «ожиданием сигнала», однако ввиду неявности этой операции синхронизации, при поиске дедлоков о ней просто забывают, как о потенциальном источнике угрозы и часто не замечают в коде.

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

Несколько слов о модели многопоточных программ

Мы назвали эту модель – «моделью переходов» и представляет она собой совокупность ориентированных графов, где каждый граф представляет собой поток (субъект). Каждый граф имеет одну начальную вершину, соответствующую состоянию, когда ни одно средство синхронизации еще не задействовано, и имеет одну конечную вершину, соответствующую состоянию, когда ни одно средство синхронизации уже не задействовано. Предполагается, что при достижении конечной вершины поток автоматически начинается сначала. Все другие вершины графов представляют собой операцию в отношении того или иного средства синхронизации, например, L (lock) – захват мьютекса, U (unlock) – отпускание мьютекса и т.д. Для доказательств утверждений важно, что модель игнорирует время между выполнениями отдельных операций в отношении средств синхронизации, расширяя тем самым возможный диапазон динамик до бесконечности. Если аудитории Хабра интересна математическая и физическая суть модели, то я готов написать отдельный пост на эту тему, а здесь… всего лишь начало долгой, но интересной истории о многопоточном программировании.

Пример модели, состоящей из одного потока (субъекта):

Рисунок 1.

В соответствии с данным рисунком, субъект может пройти по двум веткам: 0, L1, U1, 0 или 0, L1, L2, U2, U1, 0. Эта схема может рассматриваться, как конечный автомат, грамматика которого включает две фразы и . Будем считать, что время перехода между действиями в отношении средств синхронизации конечно, т.е. алгоритмически корректно. Не будем считать ошибкой синхронизации захват и удержание мьютекса в течение ожидания какого-либо действия пользователя, которое потенциально может никогда не наступить.
Для исследования программы на потенциальную возможность возникновения ситуации взаимной блокировки необходимо составить цепочки выполнения всех возможных субъектов в программе.

Простейшая взаимная блокировка с участием мьютексов

Пусть в нашей программе помимо потока (субъекта), приведенного на рисунке 1, есть еще один:

Рисунок 2.

Смею утверждать, что наша программа имеет потенциальный deadlock и даже, если ваши тестировщики утверждают, что все прекрасно работает, то вы не застрахованы от ситуации, что на другом «железе» ваша программа поведет себя вот так:

Рисунок 3.

Ничего не мешает нашим двух независимым потокам выполниться так: поток 1 успел захватить мьютекс 1, затем планировщик переключил выполнение на поток 2, который захватил мьютекс 2, и после этого оба наших потока пытаются захватить мьютексы, которые уже захвачены – deadlock!

Назовем систему потоков (субъектов) несовместимой, если существуют хотя бы один вариант наложения цепочек их выполнения, при котором наступает ситуация взаимной блокировки. Соответственно, совместимой является такая система субъектов, для которой не существует динамики, при которой возможно возникновение ситуации взаимной блокировки.
Рассмотренные два субъекта являются несовместимыми, т.к. существует вариант наложения, приведенный на рисунке 3, при котором возникает ситуация взаимной блокировки. Отметим, что такие субъекты не обязательно будут приводить к блокировке (зависанию) программы. Динамика конкретной работы (время переходов между обращениями к средствам синхронизации) может быть такова, что найденный вариант наложения никогда не проявится в реальности. В любом случае, программный код, описываемый такой моделью, является потенциально опасным и блокировки могут проявиться при портировании на другую программную или аппаратную платформу, а также просто при изменении условий функционирования.

Рисунок 4

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

Рисунок 5

На рисунке 6 показан другой вариант совмещения цепочек выполнения для модели, представленной на рисунке 5, при котором возникает взаимная блокировка.

Рисунок 6

Прямо так и хочется сказать: Как страшно жить!

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

Первое правило

Всегда отпускайте захваченные мьютексы в обратном захвату порядке, т.е. руководствуйтесь логикой «первый захвачен – последний отпущен».

Второе правило

Всегда соблюдайте один и тот же порядок захвата мьютексов.
Если вы в одном потоке захватываете мьютекс 1, а затем мьютекс 2, то недопустимо захватывать их в ином порядке в другом потоке.
На самом деле, правило не так просто, как кажется на первый взгляд. Еще раз посмотрим внимательно на Рисунок 6. Там это правило нарушено, но это несколько неочевидно. Глядя на первый поток, мы фиксируем, что мьютекс 2 мы захватываем после мьютекса 1. Глядя на второй поток, мы фиксируем, что мьютекс 3 мы захватываем после мьютекса 2. Объединение этих наблюдений означает, что мьютекс 3 мы захватываем после мьютекса 1, что не выполняется в потоке 3. Результатом этого невыполнения является deadlock, который и показан на рисунке.

Несмотря на всю простоту данных правил, смею утверждать, что если ваша программа в качестве средств синхронизации использует только мьютексы и вы соблюли эти правила, то взаимные блокировки вам не страшны! Мы, конечно, не берем в расчет ситуацию попытки двойного захвата нерекурсивного мьютекса одним потоком, но это скорее глупость, чем deadlock.

В заключение хотелось бы задать вопрос: интересна ли эта тема аудитории Хабра? Если да, то есть желание и возможность рассказать о чуть более интересных ситуациях, возникающих при использовании сигнальных переменных и, может быть, немного глубже окунуться в доказательную базу – это уже для искушенных программистов многопоточных приложений.

Надеюсь, этот пост был полезен.

Боремся с deadlock-ами: паттерн unlocked callbacks

В Википедии дается следующее определение взаимной блокировки: «Взаимная блокировка (англ. deadlock) — ситуация в многозадачной среде или СУБД, при которой несколько процессов находятся в состоянии бесконечного ожидания ресурсов, занятых самими этими процессами».

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

Классический пример взаимной блокировки: первый поток (A) захватывает мьютекс M1 и следом мьютекс M2. Второй поток (B) захватывает мьютекс M2, а уже после этого – мьютекс M1. Взаимная блокировка этих двух потоков может произойти следующим образом: поток A захватывает M1, поток B захватывает M2, после этого оба потока «обречены»: ни поток A не может захватить M2, ни поток B не может захватить M1; попытки захвата мьютексов заблокируют оба потока.

Описанная взаимная блокировка произойдет только в том случае, если оба потока успеют захватить ровно по одному мьютексу. В противном случае потоки продолжат свое выполнение.

Данная ситуация очень распространена в сложных многопоточных системах. Как правило, мьютексы-участники расположены далеко друг от друга (в различных компонентах системы), и выявить участников взаимной блокировки оказывается достаточно сложно.

Распространенная ситуация в реальной системе
  • некоторый объект (Worker) осуществляет некоторую деятельность в отдельном потоке; объект Worker в начале своего существования создает этот поток, и сам же его завершает перед своим уничтожением;
  • деятельность потока заключается в циклическом повторении некоторых действий (например, получение данных из сети);
  • результаты этих отдельных действий выдаются потребителям через функции или интерфейсы обратного вызова (callback);
  • в любой момент жизни объекта Worker может появиться новый потребитель или исчезнуть существующий; объект Worker ведет список потребителей и предоставляет функции для регистрации и удаления потребителей (registerCallback и unregisterCallback соответственно);
  • список потребителей является разделяемым ресурсом: к нему имеет доступ внутренний поток объекта Worker, а также потребители, которые делают вызовы registerCallback и unregisterCallback из контекста своего потока;
  • так как список потребителей является разделяемым ресурсом, он защищается с помощью мьютекса, которым владеет объект Worker; при захваченном мьютексе выполняется модификация списка потребителей; при захваченном мьютексе производится передача данных потребителям из объекта Worker.
  • внутри одного из потребителей объекта Worker делается захват мьютекса M (с последующим его освобождением, разумеется);
  • еще один объект системы при захваченном мьютексе M регистрирует себя в качестве потребителя объекта Worker.

Далее предлагаются два способа решения этой проблемы.

Способ 1: изменить порядок блокировки
  1. Сначала (заблоговременно) будет блокироваться внутренний мьютекс объекта Worker.
  2. Уже после этого потребитель будет выполнять действия, требующие захвата мьютекса M.
  3. Потребитель зарегистрируется у объекта Worker; захват внутреннего мьютекса внутри объекта Worker не производится.
  • на каждого потребителя ложится бремя предварительных действий перед работой с объектом Worker; вероятность ошибки со стороны программиста увеличивается;
  • не всегда такое возможно вообще: а что, если с помощью мьютекса M обеспечивался безопасный доступ к указателю на объект Worker?
  • «кишки наружу» — это просто некрасиво.
Способ 2: не блокировать мьютекс при передаче данных потребителям

Этот способ звучит многообещающе: если передача данных из внутреннего потока объекта Worker потребителям будет происходить при не заблокированных мьютексах, это исправит все возможные в дальнейшем взаимные блокировки при работе с объектом Worker.

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

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

  • поток объекта Worker перед вызовом интерфейсов зарегистрированных потребителей должен создать копию списка потребителей; копия создается при захваченном мьютексе, для передачи данных потребителям поток делает цикл по копии списка;
  • нужно обеспечить, чтобы после завершения вызова unregisterCallback никакие данные потребителю не передавались; так как копия списка потребителей создается при каждом вызове из потока потребителям, риск «запоздалого вызова» существует только один раз, если unregisterCallback вызван в ходе прохода по копии списка потребителей;
  • и, таким образом, необходимо как-то идентифицировать состояние, когда поток объекта Worker выполняет передачу данных потребителям и работает с копией списка потребителей; если unregisterCallback вызван, когда поток находится в этом состоянии, возврат из unregisterCallback должен быть задержан до того момента, когда завершится передача данных потребителям.
  • объект Worker содержит:
    • список потребителей;
    • флаг, означающий, что поток в настоящий момент выполняет передачу данных потребителям;
    • мьютекс для разграничения доступа к списку потребителей и флагу;
    • условную переменную для организации ожидания и пробуждения потребителя, который вызвал unregisterCallback «не вовремя»;
    • захватывает мьютекс;
    • создает копию списка потребителей; копия создается в стеке потока;
    • устанавливает флаг в значение true;
    • освобождает мьютекс;
    • захватывает мьютекс;
    • сбрасывает флаг в значение false;
    • с использованием условной переменной пробуждает все ожидающие потоки;
    • освобождает мьютекс;
    • захватывает мьютекс;
    • удаляет потребителя из списка;
    • до тех пор, пока флаг установлен, ожидает на условной переменной; атомарно с переходом в состояние ожидания освобождается мьютекс, и атомарно с пробуждением мьютекс захватывается;
    • освобождает мьютекс.
    Реализация с использованием средств синхронизации библиотеки Qt

    Заголовочный файл:

    class ICallback < public: virtual void dataReady(QByteArray data) = 0; >; class Worker : public QThread < public: Worker(); void registerCallback(ICallback *callback); void unregisterCallback(ICallback *callback); protected: virtual void run(); private: QMutex _mutex; QWaitCondition _wait; bool _callingNow; QLinkedList_callbacks; >; 
    Worker::Worker() : QThread(), _mutex(QMutex::NonRecursive), _callingNow(false) < . >void Worker::registerCallback(ICallback *callback) < QMutexLocker locker(&_mutex); _callbacks.append(callback); >void Worker::unregisterCallback(ICallback *callback) < QMutexLocker locker(&_mutex); _callbacks.removeOne(callback); if(QThread::currentThread()!=this) < while(_callingNow) _wait.wait(&_mutex); >> void Worker::run() < while(. ) < QByteArray data; . QLinkedListcallbacksCopy; _mutex.lock(); _callingNow=true; callbacksCopy=_callbacks; _mutex.unlock(); for(QLinkedList::const_iterator it=callbacksCopy.begin(); it!=callbacksCopy.end(); ++it) < (*it)->dataReady(data); > _mutex.lock(); _callingNow=false; _wait.wakeAll(); _mutex.unlock(); > > 
    • Блог компании Нордавинд
    • Проектирование и рефакторинг

    Что такое взаимная блокировка deadlock

    В заимная блокировка (deadlock) —это ситуация в СУБД, при которой двое или более сеансов находятся в состоянии бесконечного ожидания ресурсов, захваченных самими этими же сеансами. При обычном состоянии, когда один из сеансов захватывает, какой либо ресурс, другие сеансы будут ожидать его освобождения, выстраиваясь в очередь, друг за другом. Но если удерживающий сеанс не может освободить ресурс, вследствие того что он ожидает освобождения ресурса, захваченного одним из ожидающих сеансов, возникает парадоксальная ситуация, при которой ни один из захваченных сеансами ресурсов не может быть освобождён. В этом случае СУБД сама должна вмешаться в процесс бесконечного ожидания и принудительно освободить запрос в сеансе к одному из ресурсов. При этом никак не должна пострадать уже начатая транзакция сеанса. СУБД должна только отменить последнее действие в сеансе, которое привело к сложившейся ситуации взаимного блокирования, и предоставить сеансу решить, зафиксировать ли изменения в данных или отменить их.

    Надо сказать, что возникновение взаимного блокирования это исключительный случай для СУБД. У Oracle даже для этого случая есть специальная ошибка ORA-00060. Если взаимные блокировки возникают очень редко, то можно просто игнорировать эту ошибку, обрабатывая в приложении данное исключение. Но когда подобная ошибка начинает возникать очень часто, требуется детальный анализ возникающей ситуации. Большую помощь в этом нам может оказать файл трассировки, который создаёт Oracle в каждом случае возникновения взаимных блокировок. Образуется этот файл в каталоге, который определяется параметром инициализации user_dump_dest. Кроме создания файла, Oracle делает так же запись о возникшей ошибке в системный журнал alert.log. В этом журнале наряду с самим фактом происшедшей ошибки записывается ссылка к образованному файлу трассировки. Так как данный журнал и файл доступны только администратору базы данных, одним из пунктов его обязанностей, должно являться обнаружение подобных записей, и предоставление разработчику приложения всей доступной информации для исправления повторяющихся взаимных блокировок.

    Граф ожидания транзакций

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

    В Oracle, впрочем, как и в других современных СУБД, поиск ситуаций взаимного блокирования происходит постоянно. Вначале строится так называемый граф ожидания транзакций. Граф состоит из вершин и соединяющих их рёбер. Существуют два типа вершин – это вершины соответствующие транзакциям или сеансам, и вершины, представляющие из себя ресурсы или объекты. Ребра в данном случае представляют собой блокировки. Если блокировка захвачена, то ребро направлено от вершины соответствующей сеансу к вершине определяющей ресурс. Если же блокировка ожидает установки, то, наоборот, ребро направлено от вершины ресурса к вершине соответствующей сеансу. Если в этом сплетении рёбер и вершин обнаруживается цикл, то это означает, что возникла ситуация взаимного блокирования. При этом Oracle должен выбрать и отменить одно из ожидающих рёбер, что приведёт к разрыву цикла и нормализации ситуации.

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

    Сценарии возникновения

    Сценариев возникновения взаимных блокировок в Oracle на самом деле не так уж много. Их отличие друг от друга заключается в видах удерживающих и ожидающих блокировок, а так же в режимах в которых эти блокировки находятся. Ниже, мы попробуем смоделировать большинство ситуаций, при которых возникают взаимные блокировки, а также попытаемся детально разобраться в механизме самого взаимного блокирования. Научимся разбирать, анализировать содержимое трассировочного файла, и определять, к какому сценарию взаимного блокирования относиться данная взаимная блокировка.

    Блокировки транзакций. Захват и ожидание в исключительном режиме.

    Рассмотрим первый сценарий взаимного блокирования. Необходимым условием для его возникновения является наличие в двух разных сеансах установленной и ожидающей блокировок транзакций (TX) в исключительном режиме. Моделировать ситуацию будем с использованием редакции Oracle Express Edition и инструмента администратора ZhiSQL for Oracle.

    Механизм взаимоблокировки

    Вначале создадим тестового пользователя zh и выдадим ему все необходимые привилегии:

    Подключение к: Oracle Database 10g Express Edition Release 10.2.0.1.0 – Production SYSTEM@XE> CREATE USER zh IDENTIFIED BY test DEFAULT TABLESPACE users; Пользователь создан SYSTEM@XE> GRANT connect, resource, alter session TO zh; Grant succeeded

    Далее, создадим простую таблицу и вставим в неё две строки:

    ZH@XE> CREATE TABLE t1 (c1 NUMBER PRIMARY KEY, c2 VARCHAR2(50)); Таблица создана ZH@XE> INSERT INTO t1 (c1) VALUES(1); Вставлено: 1 строка ZH@XE> INSERT INTO t1 (c1) VALUES(2); Вставлено: 1 строка ZH@XE> COMMIT; Commit complete

    Образуем два сеанса и для наглядности разбора ситуации включим в первом из них трассировку на уровне событий ожидания. Далее, в этом же сеансе будем последовательно изменять первую и вторую строки, а во втором проделаем те же действия, но в обратном порядке.

    ZH@XE(28)> ALTER SESSION SET EVENTS '10046 trace name context forever, level 12'; Session altered ZH@XE(28)> UPDATE t1 SET c2 = 'Строка1' WHERE c1 = 1; Изменено: 1 строка
    ZH@XE(24)> UPDATE t1 SET c2 = 'Строка2' WHERE c1 = 2; Изменено: 1 строка

    В результате выполненных нами действий в существующих сеансах были открыты две транзакции. В первом сеансе была выставлена блокировка транзакции (TX) на первую строку в исключительном режиме. Такая же блокировка выставлена и во втором сеансе, но на вторую строку. Убедиться в этом, мы можем, сделав небольшой запрос к системному представлению v$lock:

    SYSTEM@XE> SELECT * FROM v$lock WHERE sid IN (24, 28) AND type = 'TX'; ADDR KADDR SID TYPE ID1 ID2 LMODE REQUEST CTIME BLOCK -------- -------- --- ---- ------ --- ----- ------- ----- ----- 296EE5D8 296EE6F4 28 TX 262166 169 6 0 1110 0 296FA680 296FA79C 24 TX 327718 163 6 0 879 0

    Рассмотрим более подробно содержимое этого запроса. Столбец SID здесь содержит идентификаторы первого и второго сеансов (28 и 24). Содержимое столбца TYPE указывает на тип блокировки, в нашем случае блокировки транзакции (TX). Столбец LMODE хранит значение 6, что соответствует установившемуся исключительному режиму блокировки. В результате мы видим, что в каждом из сеансов имеется по одной TX блокировке в установившемся исключительном режиме. Определить к какой транзакции относиться каждая из этих TX блокировок можно с помощью содержимого дополнительных столбцов ID1 и ID2. В них находятся составные части идентификатора транзакций, декодировать которые можно с помощью следующего запроса:

    SYSTEM@XE> SELECT sid, TRUNC(id1/POWER(2,16)) rbs, BITAND(id1, POWER(2,16)-1)+ 0 slot, id2 seq FROM v$lock WHERE sid IN (24, 28) AND type = 'TX'; SID RBS SLOT SEQ --- --- ---- --- 28 4 22 169 24 5 38 163 Выбрано: 2 строки

    Расшифровав значения столбцов ID1 и ID2, мы получили номер сегмента отката, слот и номер изменения транзакции. Эти значения полностью совпадают со значениями из представления v$transaction и все вместе, в шестнадцатеричном виде, представляют собой идентификатор транзакции:

    SYSTEM@XE> SELECT s.sid, t.xidusn, xidslot, xidsqn FROM v$transaction t, v$session s WHERE t.addr = s.taddr; SID XIDUSN XIDSLOT XIDSQN --- ------ ------- ------ 24 5 38 163 28 4 22 169 Выбрано: 2 строки

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

    ZH@XE(28)> UPDATE t1 SET c2 = 'Строка2' WHERE c1 = 2;

    Сеанс находиться в ожидании. Оно возникло от того, что первый сеанс пытается установить TX блокировку в исключительном режиме на вторую строку, которая уже захвачена TX блокировкой второго сеанса. Если в это время заглянуть в файл трассировки первого сеанса, то мы увидим там следующие строки:

    WAIT #1: nam='enq: TX - row lock contention' ela= 3000022 name|mode=1415053318 usn
    

    В сеансе постоянно возникает ожидание “конкуренция блокировки строки”. В параметрах этого ожидания мы видим уже знакомые нам значения идентификатора транзакции второго сеанса (slot, sequence). Именно эта транзакция, установила ранее TX блокировку в исключительном режиме на вторую строку и привела к ожиданию. Более детально это можно просмотреть в содержимом представления v$lock:

    SYSTEM@XE> SELECT * FROM v$lock WHERE sid IN (24, 28) AND type = 'TX'; ADDR KADDR SID TYPE ID1 ID2 LMODE REQUEST CTIME BLOCK -------- -------- --- ---- ------ --- ----- ------- ----- ----- 2A201720 2A201734 28 TX 327718 163 0 6 1074 0 296EE5D8 296EE6F4 28 TX 262166 169 6 0 7048 0 296FA680 296FA79C 24 TX 327718 163 6 0 6817 1 Выбрано: 3 строки

    Как мы видим, в представлении появилась новая строка о TX блокировке со значением 6 в поле REQUEST. Данное значение означает, что сеанс 28 запросил установку TX блокировки на строку в исключительном режиме. При этом значения столбцов ID1 и ID2 этого запроса содержат идентификатор транзакции сеанса 24. Это свидетельствует о том, что первый сеанс ожидает освобождения строки захваченной транзакцией именно второго сеанса.

    И так, на данный момент, мы имеем классическую картину ожидания. Но что произойдет, если мы изменим во втором сеансе первую строку? Ведь она уже захвачена первым сеансом:

    ZH@XE(24)> UPDATE t1 SET c2 = 'Строка1' WHERE c1 = 1;

    Возникает ожидание, и мы становимся свидетелями типичного случая взаимного блокирования. Второй сеанс ожидает освобождения первой строки, заблокированной в результате начатой транзакции первого сеанса. В то же время первый сеанс ожидает освобождение второй строки, заблокированной транзакцией второго сеанса. Такое ожидание могло бы продолжаться вечно, но как мы рассматривали ранее, Oracle сам определяет подобные возникающие тупиковые ситуации в течение примерно трёх секунд. В нашем случае он просто отменит выполнение последнего оператора UPDATE в первом сеансе и сгенерирует следующую ошибку:

    ZH@XE(28)> UPDATE t1 SET c2 = 'Строка2' WHERE c1 = 2; UPDATE t SET c2 = 'Строка2' WHERE c1 = 2 * Ошибка в строке 1: ORA-00060: deadlock detected while waiting for resource 

    Убедимся в том, что транзакции отменены не были. Для этого выполним следующий запрос:

    SYSTEM@XE> SELECT s.sid, t.status, t.start_time, t.xid FROM v$transaction t, v$session s WHERE t.addr = s.taddr; SID STATUS START_TIME XID --- ------ ----------------- ---------------- 24 ACTIVE 01/21/10 23:14:40 05002600A3000000 28 ACTIVE 01/21/10 23:10:49 04001600A9000000 Выбрано: 2 строки

    Как видно из результатов запроса, транзакции по-прежнему активны. Отменять их полностью у Oracle нет необходимости, достаточно лишь просто вернуться в одном из сеансов к неявной точке сохранения, которая делается перед каждым DML оператором.

    Теперь мы должны решить, что нам делать с этой незавершённой транзакцией в первом сеансе. Повторять отменённый оператор не имеет смысла. Ситуация взаимного блокирования повториться, и ошибка возникнет уже в другом сеансе. Поэтому нам остаётся либо произвести отмену, либо зафиксировать транзакцию. После этого второй сеанс продолжит выполнение оператора UPDATE, так как необходимая строка для этого будет освобождена.

    Что же касается самой Oracle, то в результате всех перечисленных выше действий приведших к возникновению взаимной блокировки, в файл журнала alert_xe.log будет занесена запись следующего вида:

    Fri Jan 22 01:09:58 2010 ORA-00060: Deadlock detected. More info in file c:\oraclexe\app\oracle\admin\xe\udump\xe_ora_2480.trc.

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

    Трассировочный файл взаимной блокировки

    Сам файл трассировки содержит множество данных, в том числе здесь находится и полный дамп состояния процессов Oracle на момент возникновения взаимоблокировки. Но нам важны только несколько секций файла. Первая из них – это текущий SQL оператор сеанса, который столкнулся с ошибкой взаимной блокировки и был отменён. Для этого находим в файле строку DEADLOCK DETECTED. Чуть ниже её, после ключевых слов «Current SQL statement for this session» будет находиться необходимая нам секция:

    Current SQL statement for this session: UPDATE t1 SET c2 = 'Строка2' WHERE c1 = 2

    Вторая секция, которая нас заинтересует – это граф взаимной блокировки. Он находиться после ключевой строки Deadlock graph, и отображает цепочку захватов и ожиданий блокировок между сеансами:

    Deadlock graph: ---------Blocker(s)-------- ---------Waiter(s)--------- Resource Name process session holds waits process session holds waits TX-00040016-000000a9 20 28 X 21 24 X TX-00050026-000000a3 21 24 X 20 28 X

    Вспомним, как Oracle обнаруживает блокировки. Для этого он постоянно строит граф ожидания транзакций. Если обнаружен цикл в этом графе, то это означает, что возникла взаимная блокировка. Так вот именно этот цикл и отображается в секции Deadlock graph, правда в очень специфическом виде.

    Визуально граф представлен в виде таблицы, которая разделена на три логических блока. Первый блок обозначает ресурсы, участвующие во взаимной блокировке. В нашем случае таким ресурсом является блокировка транзакции. Ресурс обозначается символьным кодом TX, после которого идет идентификатор транзакции в шестнадцатеричном виде. Именно этот идентификатор, но только в десятичном виде мы получили, когда расшифровывали значения столбцов ID1 и ID2 представления v$lock. Второй блок состоит из столбцов, содержащих информацию о сеансах удерживающих данную блокировку, а также режим, в котором эта блокировка установлена. И наконец, третий блок аналогичен второму, но противоположен по содержанию. Он хранит информацию о сеансах, которые сделали запрос на установление блокировки, но вынуждены ожидать освобождения блокировки из второго блока.

    Расшифровка графа не представляет сложности. Для этого нам надо проанализировать содержимое таблицы построчно, слева направо. К примеру, для нашего случая это будет выглядеть следующим образом. Транзакционная блокировка TX-00040016-000000a9 на строку удерживается сеансом 28 (поле session) в исключительном режиме (символ X в поле holds). Сеанс 24 одновременно ждёт освобождение этого ресурса, чтобы установить свою TX блокировку в исключительном режиме (символ X в поле waits).

    Пока это нормальное ожидание необходимого ресурса. Поэтому далее мы обратимся ко второй строке графа. Здесь транзакционная блокировка TX-00050026-000000a3 на строку удерживается сеансом 24 в исключительном режиме, а сеанс 28 ждёт освобождение строки, чтобы установить свою TX блокировку в исключительном режиме. В то же время в первой строке графа сеанс 24 уже ожидает освобождения ресурса, в результате чего получается, что сеансы находятся в состоянии бесконечного ожидания. Единственным логичным действием в этом случае, явилось бы отмена ожидания установки блокировки в сеансе 28, что собственно и было сделано Oracle. В графе такое отменённое ожидание всегда отображается последним в блоке Waiter(s).

    Итак, граф расшифрован. Он дал нам описание цепочки захватов и ожиданий TX блокировок в сеансах. Но по этой цепочке мы можем судить только об общей картине возникновения взаимной блокировки. Если же нам потребуется найти конкретные ресурсы, из-за которых возникают ожидания, сделать нам это будет затруднительно. К счастью Oracle сам позаботился об этом, записав в файл трассировки информацию о строках, освобождения которых от TX блокировок ожидают сеансы. Рассмотрим более подробно эту секцию. Найти её можно сразу после графа, по ключевой строке Rows waited on:

    Rows waited on: Session 24: obj - rowid = 000035C6 - AAADXGAAEAAAAFkAAA (dictionary objn - 13766, file - 4, block - 356, slot - 0) Session 28: obj - rowid = 000035C6 - AAADXGAAEAAAAFkAAB (dictionary objn - 13766, file - 4, block - 356, slot - 1)

    В этой секции для каждого ожидающего сеанса, который перечислен в графе, указана строка, на которую этот сеанс пытается получить TX блокировку. Строка идентифицируется номером объекта, которому она принадлежит, и идентификатором ROWID. Чуть ниже дана их полная расшифровка в десятичном виде. Это позволяет, с лёгкостью, обратившись, например, к системному представлению dba_objects, идентифицировать объект, к которому принадлежит данная строка:

    SYS@XE> SELECT owner, object_name FROM dba_objects WHERE object_id = 13766; OWNER OBJECT_NAME ----- ----------- ZH T1

    Следующая секция трассировочного файла, которую мы рассмотрим, хотя и не столь важна, но позволяет дополнить картину взаимной блокировки. Она располагается сразу за секцией Rows waited on и находится по следующим ключевым словам:

    Information on the OTHER waiting sessions: Session 24: pid=21 serial=48 audsid=141 user: 39/ZH O/S info: user: ALFA\Сергей, term: ALFA, ospid: 1984:2524, machine: program: DBASQL.exe client info: DBASQL application name: DBASQL.exe, hash value=0 Current SQL Statement: UPDATE t1 SET c2 = 'Строка1' WHERE c1 = 1 End of information on OTHER waiting sessions.

    Из этой секции можно получить информацию о пользователе, приложении и текущем SQL курсоре ожидающих сеансов, вовлеченных в процесс взаимной блокировки. Иногда это может быть полезно при поиске причин образования ситуации взаимного блокирования.

    Как определить по содержимому трассировочного файла, что произошел первый сценарий взаимного блокирования? Для ответа на этот вопрос обратимся в первую очередь к графу взаимной блокировки. Для начала мы должны определить, с какого идентификатора начинаются имена ресурсов графа в столбце «Resource Name». В нашем случае это всегда будет идентификатор TX, то есть блокировка транзакции. Далее нам следует проверить значения режимов блокировок, отображаемые в столбцах holds и waits. Они должны иметь одинаковое значение равное символу X. Не следует так же забывать, что данный сценарий взаимного блокирования возникает на уровне строк, и, следовательно, в секции «Rows waited» всегда будут присутствовать данные об ожидающих строках. Отсюда следует непреложное правило о том, что в первой секции «Current SQL statement for this session» при данном сценарии вы никогда не встретите оператора INSERT, так как строки, вставленные в одном из сеансов, никогда не будут доступны для другого сеанса до фиксации транзакции.

    Выводы

    Пришло время обобщить полученную информацию, которую мы узнали, изучая взаимную блокировку, образующуюся по первому сценарию. Обычно, такая взаимная блокировка возникает из-за неодинаковой последовательности обработки строк, в пределах транзакций разных сеансов. Если бы мы, к примеру, в нашем случае проводили бы обновление строк таблицы в каждом из сеансов последовательно в соответствии со значением ключевого столбца «c1», то мы могли бы избежать взаимных блокировок. Образовалась бы простая очередь ожидания ресурса. Правда, бывают ситуации, когда одинаковую последовательность обработки строк в разных сеансах сделать трудно или даже невозможно. Как тогда выходить из ситуации? Самое простое, что приходит на ум, и о чём я уже упоминал выше, это можно обрабатывать ошибку в приложении, и в случае её возникновения в одном из сеансов откатывать транзакцию, повторяя все необходимые действия позже. Велика вероятность, что транзакция в другом сеансе за это время завершиться и не будет мешать производить изменения в данных. Вообще по возможности надо всегда уменьшать время длительности транзакций в приложениях. Если транзакция будет короткой, то это уменьшает время в течение, которого сеансы могут попасть в неприятную ситуацию с взаимным блокированием. Это справедливо и для длинной цепочки каскадного срабатывания триггеров, которым так грешат некоторые сложные системы. По возможности эту цепочку надо укорачивать.

    Похожие материалы

    • Практическое администрирование Oracle - Взаимные блокировки. Часть 4.
    • Практическое администрирование Oracle - Взаимные блокировки. Часть 3.
    • Практическое администрирование Oracle - Взаимные блокировки. Часть 2.

    Самое популярное

    • Практическое администрирование Oracle - Аудит. Часть1.
    • RMAN в примерах - Быстрый старт. Глава 1.
    • RMAN В ПРИМЕРАХ - Использование RMAN. Глава 3. Часть 2
    • RMAN В ПРИМЕРАХ - Конфигурирование окружения RMAN. Глава 4. Часть 1.
    • Дублирование базы данных с помощью RMAN. Часть 1.

    Взаимная блокировка (deadlock) в Java и методы борьбы с ней

    Java-университет

    Взаимная блокировка (deadlock) в Java и методы борьбы с ней - 1

    При разработке многопоточных приложений часто возникает дилемма: что важнее надежность или работоспособность приложения. Например, мы используем синхронизацию для поточной безопасности (thread safety), при этом в случаи, неверного порядка синхронизации, мы можем вызвать взаимную блокировки. Так же, мы используем пулы потоков и семафоры, для ограничения потребления ресурсов, при этом ошибка в таком дизайне может привести к взаимной блокировке, вследствие недостатка ресурсов. В данной статье мы поговорим о том, как избегать взаимной блокировки, а так же других проблем в работоспособности приложения. Так же мы рассмотрим, как может приложение быть написано таким образом, чтоб иметь возможность восстановится в случаи взаимной блокировки. Взаимная блокировка – это ситуация в которой, два или более процесса занимая некоторые ресурсы, пытаются заполучить некоторые другие ресурсы, занятые другими процессами и ни один из процессов не может занять необходимый им ресурс, и соответственно освободить занимаемый. Данное определение слишком общее, поэтому сложно для восприятия, для лучшего его понимания мы рассмотрим типы взаимных блокировок на примерах.

    Взаимная блокировка порядка синхронизации

    Рассмотрим следующую задачу: необходимо написать метод осуществляющий транзакцию перевода некоторого количества денег с одного счета на другой. Решение может иметь следующий вид:

     public void transferMoney(Account fromAccount, Account toAccount, Amount amount) throws InsufficientFundsException < synchronized (fromAccount) < synchronized (toAccount) < if (fromAccount.getBalance().compareTo(amount) < 0) throw new InsufficientFundsException(); else < fromAccount.debit(amount); toAccount.credit(amount); >> > > 

    На первый взгляд, данный код синхронизирован вполне нормально, мы имеем атомарную операцию проверки и изменения состояния счета-источника и изменение счета-получателя. Но, при данной стратегии синхронизации может возникнуть ситуация взаимной блокировки. Давайте рассмотрим пример того, как это происходит. Необходимо произвести две транзакции: со счета A на счет B перевести x денег, а со счета B на счет A – y. Зачастую эта ситуация не вызовет взаимной блокировки, однако, при неудачном стечении обстоятельств, транзакция 1 займет монитор счета A, транзакция 2 займет монитор счета B. Результат – взаимная блокировка: транзакция 1 ждет, пока транзакция 2 освободит монитор счета B, но для этого транзакция 2 должна получить доступ к монитору A, занятому транзакцией 1. Одна из больших проблем с взаимными блокировками – что их нелегко найти при тестировании. Даже в ситуации, описанной в примере, потоки могут не заблокироваться, то есть данная ситуация не будет постоянно воспроизводится, что значительно усложняет диагностику. В целом описанная проблема недетерминированности является типичной для многопоточности (хотя от этого не легче). Потому, в повышении качества многопоточных приложений важную роль играет code review, поскольку он позволяет выявить ошибки, которые проблематично воспроизвести при тестировании. Это, конечно же, не значит, что приложение не надо тестировать, просто о code review тоже не надо забывать. Что нужно сделать, чтобы этот код не приводил к взаимной блокировке? Данная блокировка вызвана тем, что синхронизация счетов может происходить в разном порядке. Соответственно, если ввести некоторый порядок на счетах (это некоторое правило, позволяющее сказать, что счет A меньше чем счет B), то проблема будет устранена. Как это сделать? Во-первых, если у счетов есть какой-то уникальный идентификатор (например, номер счета) численный, строчный или еще какой-то с естественным понятием порядка (строки можно сравнивать в лексикографическом порядке, то можем считать, что нам повезло, и мы всегда можем сначала занимать монитор меньшего счета, а потом большего (или наоборот).

     private void doTransfer(final Account fromAcct, final Account toAcct, final DollarAmount amount) throws InsufficientFundsException < if (fromAcct.getBalance().compareTo(amount) < 0) throw new InsufficientFundsException(); else < fromAcct.debit(amount); toAcct.credit(amount); >> public void transferMoney(final Account fromAcct, final Account toAcct, final DollarAmount amount) throws InsufficientFundsException < int fromId= fromAcct.getId(); int toId = fromAcct.getId(); if (fromId < toId) < synchronized (fromAcct) < synchronized (toAcct) < doTransfer(fromAcct, toAcct, amount)>> > > else < synchronized (toAcct) < synchronized (fromAcct) < doTransfer(fromAcct, toAcct, amount)>> > > > 

    Второй вариант, если такого идентификатора у нас нет, то придется его придумать самим. Мы можем в первом приближении сравнивать объекты по хеш-коду. Скорее всего, они будут отличаться. Но что делать, если они все же окажутся одинаковыми? Тогда придется добавить еще один объект для синхронизации. Это может выглядеть несколько изощренным, но что поделать. Да и к тому же, третий объект будет использоваться довольно редко. Результат будет выглядеть следующим образом:

     private static final Object tieLock = new Object(); private void doTransfer(final Account fromAcct, final Account toAcct, final DollarAmount amount) throws InsufficientFundsException < if (fromAcct.getBalance().compareTo(amount) < 0) throw new InsufficientFundsException(); else < fromAcct.debit(amount); toAcct.credit(amount); >> public void transferMoney(final Account fromAcct, final Account toAcct, final DollarAmount amount) throws InsufficientFundsException < int fromHash = System.identityHashCode(fromAcct); int toHash = System.identityHashCode(toAcct); if (fromHash < toHash) < synchronized (fromAcct) < synchronized (toAcct) < doTransfer(fromAcct, toAcct, amount); >> > else if (fromHash > toHash) < synchronized (toAcct) < synchronized (fromAcct) < doTransfer(fromAcct, toAcct, amount); >> > else < synchronized (tieLock) < synchronized (fromAcct) < synchronized (toAcct) < doTransfer(fromAcct, toAcct, amount) >> > > > 

    Взаимная блокировка между объектами

    Описанные условия блокировки представляют наиболее простой по диагностике случай взаимной блокировки. Зачастую в многопоточных приложениях различные объекты пытаются получить доступ к одним и тем же синхронизированным блокам. При этом может возникнуть взаимная блокировка. Рассмотрим следующий пример: приложение для диспетчера полетов. Самолеты сообщают диспетчеру, когда они прибыли на место назначения и запрашивают разрешение на посадку. Диспетчер хранит всю информацию о самолетах, летящих в его направлении, и может строить их положение на карте.

     class Plane < private Point location, destination; private final Dispatcher dispatcher; public Plane(Dispatcher dispatcher) < this.dispatcher = dispatcher; >public synchronized Point getLocation() < return location; >public synchronized void setLocation(Point location) < this.location = location; if (location.equals(destination)) dispatcher.requestLanding(this); >> class Dispatcher < private final Setplanes; private final Set planesPendingLanding; public Dispatcher() < planes = new HashSet(); planesPendingLanding = new HashSet(); > public synchronized void requestLanding(Plane plane) < planesPendingLanding.add(plane); >public synchronized Image getMap() < Image image = new Image(); for (Plane plane : planes) image.drawMarker(plane.getLocation()); return image; >> 

    Понять, что в этом код есть ошибка, которая может привести к взаимной блокировке сложнее, чем в предыдущем. На первый взгляд, в нем нет повторных синхронизаций, однако это не так. Вы, наверное, уже заметили, что методы setLocation класса Plane и getMap класса Dispatcher , являются синхронизированными и вызывают внутри себя синхронизированные методы других классов. Это в целом плохая практика. О том, как это можно исправить, речь пойдет в следующем разделе. В результате, если самолет прибывает на место, в тот же момент, как кто-то решает получить карту может возникнуть взаимная блокировка. То есть, будут вызваны методы, getMap и setLocation , которые займут мониторы экземпляров Dispatcher и Plane соответственно. Затем метод getMap вызовет plane.getLocation (в частности для экземпляра Plane , который в данный момент занят), который будет ждать освобождения монитора для каждого из экземпляров Plane . В то же время в методе setLocation будет вызван dispatcher.requestLanding , при этом монитор экземпляра Dispatcher остается занят рисованием карты. Результат – взаимная блокировка.

    Открытые вызовы

    С целью не допускать ситуаций вроде описанной в предыдущем разделе рекомендуется использовать открытые вызовы к методам других объектов. То есть, вызывать методы других объектов вне синхронизированного блока. Если с применением принципа открытых вызовов переписать методы setLocation и getMap возможность взаимной блокировки будет устранена. Выглядеть это будет, например, так:

     public void setLocation(Point location) < boolean reachedDestination; synchronized(this)< this.location = location; reachedDestination = location.equals(destination); >if (reachedDestination) dispatcher.requestLanding(this); > ……………………………………………………………………………… public Image getMap() < Setcopy; synchronized(this)< copy = new HashSet( planes); > Image image = new Image(); for (Plane plane : copy) image.drawMarker(plane.getLocation()); return image; > 

    Ресурсная взаимная блокировка

    Взаимные блокировки могут возникать так же при попытке получить доступ к некоторым ресурсам, которые может использовать одновременно только один поток. Примером может служить пул соединений с базами данных. Если некоторым потокам необходим доступ одновременно к двум соединениям, и они получают этот доступ в различном порядке, это может привести к взаимной блокировке. Принципиально, такого рода блокировки ни чем не отличаются от блокировок порядка синхронизации, кроме того, что возникают не при попытке выполнить некоторый код, а при попытке получить доступ к ресурсам.

    Как избегать взаимных блокировок?

    Безусловно, если код написан без каких-либо ошибок (примеры которых мы видели в предыдущих разделах), то взаимных блокировок в нем не будет. Но кто может поручиться, что его код написан без ошибок? Безусловно, тестирование помогает выявить значительную часть ошибок, но как мы уже видели ранее, ошибки в многопоточном коде нелегко диагностировать и даже после тестирования нельзя быть уверенным в отсутствии ситуаций взаимных блокировок. Можем ли мы как-то перестраховаться от блокировок? Ответ – да. Подобные техники применяются в движках баз данных, которым нередко необходимо восстанавливаться после взаимных блокировок (связанных с механизмом транзакций в БД). Интерфейс Lock и его реализации доступные в пакете java.util.concurrent.locks позволяют попытаться занять монитор, связанный с экземпляром данного класса методом tryLock (возвращает true, если удалось занять монитор). Пусть у нас есть пара объектов реализующих интерфейс Lock и нам необходимо занять их мониторы так, чтоб избежать взаимной блокировки. Реализовать это можно так:

     public void twoLocks(Lock A, Lock B) < while(true)< if(A.tryLock())< if(B.tryLock()) < try< //do something >finally < B.unlock(); A.unlock(); >> else < A.unlock(); >> > > 

    Как видно в этой программе мы занимаем два монитора, при этом, исключая возможность взаимной блокировки. Обратите внимание, блок try- finally необходим, поскольку классы из пакета java.util.concurrent.locks автоматически не освобождают монитор, и если в процессе выполнения вашей задачи возникло какое-то исключение, то монитор зависнет в заблокированном состоянии. Как диагностировать взаимные блокировки? JVM позволяет диагностировать взаимные блокировки отображая их в дампах потоков. Такие дампы включают информацию о том, в каком состоянии находится поток. Если он заблокирован, то дамп содержит информацию о мониторе, освобождения которого поток ожидает. Прежде чем вывести дамп потоков JVM просматривает граф ожидаемых (занятых) мониторов, и если находит циклы – добавляет информацию о взаимной блокировке, указывая участвующие мониторы и потоки. Дамп потоков с взаимной блокировкой выглядит так:

     Found one Java-level deadlock: ============================= "ApplicationServerThread": waiting to lock monitor 0x0f0d80cc (a MyDBConnection), which is held by "ApplicationServerThread" "ApplicationServerThread": waiting to lock monitor 0x0f0d8fed (a MyDBCallableStatement), which is held by "ApplicationServerThread" Java stack information for the threads listed above: "ApplicationServerThread": at MyDBConnection.remove_statement - waiting to lock (a MyDBConnection) at MyDBStatement.close - locked (a MyDBCallableStatement) . "ApplicationServerThread": at MyDBCallableStatement.sendBatch - waiting to lock (a MyDBCallableStatement) at MyDBConnection.commit - locked (a MyDBConnection) 

    Приведенный выше дамп явно показывает, что два потока, работающие с базой данных заблокировали друг друга. Для того чтоб диагностировать взаимные блокировки с помощью этой особенности JVM необходимо разместить вызовы операции дампа потоков в различных местах программы и провести тестирование приложения. Далее следует проанализировать полученные логи. В случаи если в них будет указано, что произошла взаимная блокировка, информация из дампа поможет обнаружить условия ее возникновения. В целом, следует не допускать ситуаций, приведенных в примерах взаимных блокировок. В таком случаи приложение, скорее всего, будет работать стабильно. Но не забывайте о тестировании и код ревью. Это поможет выявить неполадки, если они все же возникнут. В случаи, если вы разрабатываете систему, для которой критично восстановление поле взаимных блокировок, можно использовать метод, описанный в разделе «Как избегать взаимных блокировок?». В этом случаи может так же оказаться полезным метод lockInterruptibly интерфейса Lock из пакета java.util.concurrent.locks . Он позволяет прервать поток занявший монитор этим методом(и таким образом освободить монитор).

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *