– Cześć, Amigo! Mamy panaceum - lekarstwo na wszystkie choroby. Jak już widzieliśmy, niekontrolowane przełączanie wątków jest problematyczne.

– Dlaczego wątki nie mogą same decydować o tym, kiedy przejść do następnego wątku? Zrobić wszystko, co trzeba, a potem zasygnalizować: „Skończyłem!”?

– Umożliwienie wątkom samodzielnej kontroli nad przełączaniem się byłoby jeszcze większym problemem. Załóżmy, że masz kiepsko napisany kod i wątek nigdy nie oddaje procesora. Kiedyś tak to właśnie działało. I to był koszmar.

– Dobrze. Więc jakie jest rozwiązanie?

– Blokowanie innych wątków. Oto, jak to działa.

Stało się jasne, że wątki przeszkadzają sobie nawzajem, gdy próbują wykorzystać współdzielone obiekty i/lub zasoby. Tak jak widzieliśmy w przykładzie z wyświetlaniem danych w konsoli: jest jedna konsola i wszystkie wątki wyświetlają na niej swoje dane. Przez to robi się bałagan.

Dlatego został stworzony specjalny obiekt: mutex. Jest jak znak na drzwiach łazienki, który mówi: «wolne/zajęte». Wskazuje dwa statusy: obiekt jest dostępny lub też jest on zajęty. Używa się również terminów «zablokowany» i «odblokowany».

Kiedy jakiś wątek potrzebuje obiektu współdzielonego z innymi wątkami, sprawdza mutex związany z tym obiektem. Jeśli mutex jest odblokowany, to wątek go blokuje (zaznacza go jako «zajęty») i zaczyna korzystać z udostępnianego zasobu. Po tym, jak wątek wykona swoje zadanie, mutex zostaje odblokowany (oznaczony jako «dostępny»).

Jeśli wątek chce użyć obiektu, którego mutex jest zablokowany, wówczas wątek zostaje uśpiony na czas oczekiwania. Gdy mutex zostanie w końcu odblokowany przez zajmujący go wątek, nasz wątek natychmiast go blokuje i zaczyna wykonywać zadanie. Porównanie z oznaczeniem na drzwiach łazienki jest idealne.

– A więc jak mam stosować mutex? Czy muszę tworzyć specjalne obiekty?

– To znacznie prostsze. Twórcy Javy wbudowali mutex w klasę Object. Więc nawet nie musisz go tworzyć. Stanowi on część każdego obiektu. Oto, jak to wszystko działa:

Kod Opis
class MyClass
{
private String name1 = "Ally";
private String name2 = "Lena";

public void swap()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}
}
Metoda swap zamienia wartości zmiennych name1 i name2.

Co może się zdarzyć, jeśli zostanie ona wywołana z dwóch wątków jednocześnie?

Rzeczywiste wykonanie kodu Kod pierwszego wątku Kod drugiego wątku
String s1 = name1; //Ally
name1 = name2; //Lena
name2 = s1; //Ally

String s2 = name1; //Lena
name1 = name2; //Ally
name2 = s2; //Lena
String s1 = name1;
name1 = name2;
//wykonywany jest inny wątek
name2 = s1;
//wątek czeka, aż mutex zostanie odblokowany

String s2 = name1;
name1 = name2;
//wykonywany jest inny wątek
//wykonywany jest inny wątek
name2 = s2;
Konkluzja
Wartości zmiennych były zamieniane dwukrotnie, wracając na swoje pierwotne miejsca.

Zwróć uwagę na słowo kluczowe synchronized.

– Właśnie, co ono oznacza?

– Gdy wątek wejdzie w blok kodu oznaczony jako synchronized, maszyna Java natychmiast blokuje mutex obiektu wskazanego w nawiasie po słowie synchronized. Żaden inny wątek nie może wejść do tego bloku, dopóki nasz wątek z niego nie wyjdzie. Jak tylko nasz wątek opuści blok oznaczony jako synchronized, mutex zostaje od razu automatycznie odblokowany i będzie dostępny dla innego wątku.

Jeśli mutex jest zajęty, to nasz wątek zatrzyma się i zaczeka, aż będzie dostępny.

– Takie to proste i eleganckie. Co za wspaniałe rozwiązanie.

– Tak. Jak myślisz, co się stanie, gdy skończysz studia i zaczniesz szukać pracy?

Kod Opis
class MyClass
{
private String name1 = "Ally";
private String name2 = "Lena";

public void swap()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}

public void swap2()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}
}
Metody swap i swap2 mają jeden wspólny mutex (obiekt this).

Co się stanie, jeśli jeden wątek wywoła metodę swap, a inny wątek metodę swap2?

– Ponieważ mutex jest taki sam, drugi wątek będzie musiał zaczekać, aż pierwszy wątek opuści blok synchronized. Nie będzie więc żadnych problemów z równoczesnym dostępem.

– Dobra robota, Amigo! To jest prawidłowa odpowiedź!

Chciałbym jeszcze dodać, że synchronized może być używane do oznaczania nie tylko bloków kodu, ale także metod. Oto, co to oznacza:

Kod Co się dzieje naprawdę
class MyClass
{
private static String name1 = "Ally";
private static String name2 = "Lena";

public synchronized void swap()
{
String s = name1;
name1 = name2;
name2 = s;
}

public static synchronized void swap2()
{
String s = name1;
name1 = name2;
name2 = s;
}
}
class MyClass
{
private static String name1 = "Ally";
private static String name2 = "Lena";

public void swap()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}

public static void swap2()
{
synchronized (MyClass.class)
{
String s = name1;
name1 = name2;
name2 = s;
}
}