Иногда бывает так, что нескольким заданиям в системе необходим доступ к одному и тому же объекту или управление им. В интерфейсе ILock определен протокол, обеспечивающий исключительный доступ к общему объекту. Если заданию необходим доступ к общему объекту, оно запрашивает блокировку для этого объекта. По окончании работы с объектом оно освобождает блокировку.
Блокировка обычно создается при создании общего объекта или при первом обращении модуля к нему. То есть, код, содержащий ссылку на общий объект, также содержит ссылку и на его блокировку. Сейчас мы создадим блокировку myLock для управления доступом к объекту myObject:
... myObject = initializeImportantObject(); IJobManager jobMan = Platform.getJobManager(); myLock = jobMan.newLock(); ...
Надежная реализация ILock предоставляется платформой. Диспетчер заданий предоставляет клиентам экземпляры этой блокировки. Эти блокировки знают друг о друге, что позволяет избежать взаимного блокирования. (Подробнее об этом поговорим чуть дальше.)
Всякий раз, когда в коде задания требуется доступ к myObject, сначала следует получить его блокировку. В следующем фрагменте кода показан общий принцип работы с блокировками:
... // Мне нужно управление объектом myObject, поэтому я сначала получаю его блокировку. try { myLock.acquire(); updateState(myObject); // действия с объектом } finally { lock.release(); } ...
Метод acquire() не вернет значения до тех пор, пока вызывающее задание не получит исключительного доступа к блокировке. Другими словами, если несколько заданий уже получили блокировку, то этот код будет заблокирован до тех пор, пока блокировка не освободится. Обратите внимание, что код, который запрашивает блокировку и манипулирует myObject, заключен в блок try, поэтому при возникновении любой исключительной ситуации во время работы объекта блокировка может освободиться.
Довольно просто, не так ли? К счастью, блокировки действительно очень просты в использовании. Они, кроме этого, реентерабельны, что означает, что вам не нужно заботиться о том, что задание будет запрашивать несколько раз одну и ту же блокировку. Каждая блокировка снабжена счетчиком количества запросов и освобождений для конкретной нити, и будет снята с задания только тогда, когда количество освобождений сравняется с количеством запросов.
Мы уже знаем, что блокировка предоставляется диспетчером заданий, и что блокировки знают друг о друге, тем самым позволяя избежать взаимного блокирования. Давайте разберемся, что же такое тупик и как он возникает, с помощью простого сценария. Допустим, "Задание A" запрашивает "Блокировку A", а потом пытается запросить "Блокировку B." "Блокировка B" тем временем находится у "Задания B", которое заблокировано ожиданием "Блокировки A." Такой тип тупиковой ситуации свидетельствует о непродуманном использовании блокировок. Хотя этого простого случая можно избежать достаточно просто, вероятность случайного возникновения тупиковой ситуации растет с ростом количества заданий и блокировок в вашей разработке.
К счастью, платформа помогает выявить тупиковые ситуации. Когда диспетчер заданий обнаруживает тупиковые условия, он выдает диагностическую информацию в протокол, описывающий тупик. Затем он устраняет тупик предоставлением ожидающим заданиям временного доступа к блокировке, принадлежащей заблокированному заданию. Очень важно внимательно проверять код, в котором используется несколько блокировок, и исправить все тупиковые ситуации, если таковые обнаружатся платформой.