Blokady

Możliwa jest sytuacja, w której wiele zadań w systemie będzie wymagać dostępu i możliwości manipulowania tym samym obiektem. Interfejs ILock definiuje protokół, który pozwala przyznać wyłączny dostęp do obiektu współużytkowanego. Gdy zadanie wymaga dostępu do takiego obiektu, uzyskuje blokadę do tego obiektu. Po zakończeniu pracy z obiektem zwalnia blokadę.

Blokadę tworzy się zwykle wraz z utworzeniem obiektu współużytkowanego lub przy pierwszej próbie uzyskania dostępu do niego przez moduł dodatkowy. Oznacza to, że kod odwołujący się do obiektu współużytkowanego zawiera również odwołanie do jego blokady. Na początek utworzona zostanie blokada (myLock), która posłuży do sterowania dostępem do obiektu myObject:

   ...
   myObject = initializeImportantObject();
   IJobManager jobMan = Platform.getJobManager();
   myLock = jobMan.newLock();
   ...

Platforma udostępnia stabilną implementację interfejsu ILock. Menedżer zadań udostępnia instancje tej blokady do użycia przez klientów. Blokady te dysponują informacją o swoim wzajemnym istnieniu, co pozwala uniknąć zakleszczenia (stwierdzenie to zostanie wyjaśnione dalej).

Gdy kod zadania wymaga dostępu do obiektu myObject, musi najpierw uzyskać blokadę tego obiektu. Typowy sposób pracy z blokadą przedstawiono w kolejnym fragmencie kodu:

...
// możliwość pracy z obiektem myObject wymaga najpierw uzyskania blokady
  try {
	myLock.acquire();
	updateState(myObject);  // manipulowanie obiektem
   } finally {
	lock.release();
}
...

Metoda acquire() nie zwróci wartości, dopóki zadanie wywołujące nie uzyska wyłącznego dostępu do blokady. Innymi słowy, jeśli inne zadanie uzyskało już tę blokadę, bieżący kod zostanie zablokowany aż do momentu udostępnienia blokady. Należy zauważyć, że kod, który uzyskuje blokadę i manipuluje obiektem myObject jest otoczony blokiem try, tak aby blokada została zwolniona w razie wystąpienia jakichkolwiek wyjątków podczas pracy z obiektem.

Przykład ten nie wydaje się specjalnie skomplikowany. Korzystanie z blokad jest bowiem stosunkowo proste. Blokady są również wielobieżne, co oznacza, że nie trzeba się martwić o to, że zadanie uzyska tę samą blokadę wielokrotnie. Dla każdej blokady w związku z określonym wątkiem przechowywany jest licznik operacji jej uzyskania i zwolnienia. Zwolnienie może nastąpić tylko pod warunkiem, że liczba zwolnień jest równa liczbie operacji uzyskania blokady.

Zakleszczenie

Jak wspomniano już wcześniej, blokady udostępniane przez menedżera zadań dysponują informacjami o swoim wzajemnym istnieniu i pozwalają unikać zakleszczeń. Aby zrozumieć, jak dochodzi do zakleszczenia, można przyjrzeć się prostemu scenariuszowi. Załóżmy, że "Zadanie A" uzyskuje "Blokadę A", a następnie próbuje uzyskać "Blokadę B". Tymczasem "Blokada B" jest zajęta przez "Zadanie B", które jest zablokowane w oczekiwaniu na "Blokadę A". Zakleszczenie tego typy wskazuje na ukryty problem wynikający ze sposobu, w jaki zaprojektowano użycie blokad w tych zadaniach. Choć tego prostego przypadku można by łatwo uniknąć, prawdopodobieństwo przypadkowego wprowadzenia zakleszczenia wzrasta wraz ze zwiększeniem liczby zadań i blokad w projekcie użytkownika.

Na szczęście platforma oferuje pomoc w zakresie identyfikacji zakleszczeń. Gdy menedżer zadań wykrywa zakleszczenie, wyświetla w protokole informacje diagnostyczne opisujące warunki, w jakich do niego doszło. Następnie eliminuje zakleszczenie przez tymczasowe udzielenie dostępu do blokad należących do zablokowanego zadania innym zadaniom oczekującym na dane blokady. Należy uważnie przetestować każdą implementację, w której użyto większej liczby blokad, i rozwiązać zakleszczenia sygnalizowane przez platformę.