W Javie nietrudno natrafić na metody z klasy Object
, bo pojawiają się one w kontekście dowolnego obiektu, do którego się odwołujemy. Wśród tych metod można zauważyć kilka, których przeznaczenie nie jest zrazu oczywiste; są to wait
, notify
i notifyAll
. Lektura dokumentacji może nas pouczyć, że mają one coś wspólnego z wątkami… Ale czemu coś takiego jak wątki ma swoje “wtyki” już na tak niskim poziomie? Ano dlatego, że w Javie wielowątkowość jest pewnego rodzaju młotkiem, którego posiadanie zamienia wiele napotkanych zadań i problemów w gwoździe. Opowiem dzisiaj co nieco o podstawach posługiwania się tym tępym narzędziem :)
Zacznijmy od tego, że kiedy rozdzielamy program na więcej niż jeden wątek, prawie natychmiast pojawia się problem synchronizacji. Może on mieć wiele form, ale zaryzykuję stwierdzenie, że najczęściej występują dwie poniższe:
Implementacje pierwszego przypadku nazywa się powszechnie sekcjami krytycznymi. W Javie siedzą one tak głęboko, że są częścią samego języka i mają swoje własne słowo kluczowe: synchronized
. (Jest to właściwie dokładny odpowiednik lock
z C#). Słowem tym możemy oznaczać metody lub bloki kodu, czyniąc je kodem synchonizowanym. Taki kod może być – dla danego obiektu – wykonywany tylko przez jeden wątek naraz. Wszystko więc, do czego się w takim kodzie odwołujemy, jest chronione przed jednoczesnym dostępem wielu wątków – tak jak w przykładzie poniżej:
I tak mniej więcej przedstawiaj się podstawy wielowątkowości w Javie. Bardziej zaawansowane obiekty sychronizacyjne – jak choćby te z pakietu java.util.concurrent
wykorzystują te własnie podstawowe mechanizmy zapewniania wyłączności oraz powiadamiania. Jeśli piszemy kod, który korzysta z współbieżności w prosty sposób, możemy wykorzystać je bezpośrednio.
Te ify w środku przykładu z notify trzeba zamienić na while!
notify wybudzi jakiś wątek, ale nie zapewnia, że jeżeli ktoś inny czekał na wejście do monitora, nie uzyska do niego dostępu przed wybudzonym wątkiem.
Tym samym inny wątek może “skraść” nam monitor, coś pomieszać (np. coś wyprodukować tak że data != null) i jak wyjdzie z monitora, to wybudzony wcześniej wątek już nie sprawdzi drugi raz warunku i nadpisze nieskonsumowane data.
To wszystko prawda, jeśli producentów lub konsumentów jest więcej niż po jednym. Tu dla uproszczenia pokazałem wariant 1-1, bo chciałem raczej pokazać podstawowe sychronizacyjne API w Javie, a nie zagłębiać się w różne schematy współbieżności :)