Podstawowa wielowątkowość w Javie

2010-12-17 22:57

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:

  • ochrona jakiegoś zasobu (w bardzo ogólnym sensie) przez równoczesnym dostępem z więcej niż jednego wątku
  • oczekiwanie wątków na jakieś zdarzenia i ich powiadamianie, gdy te zdarzenia zachodzą

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:

  1. public class Counter {
  2.     public static final int MAX = 10;
  3.     private int i = 0;
  4.    
  5.     public synchronized void add(int k) {
  6.         if (i + k <= MAX) i += k;
  7.     }
  8.     public synchronized int get() { return i; }
  9. }&#91;/java&#93;
  10. Chroniona jest tu wartość zmiennej <code>i</code> licznika, dzięki czemu nie ma możliwości, że przekroczy ona ustalone maksimum. Bez synchronizacji mogłoby to się stać, gdyby jeden wątek zwiększył jego wartość tuż po tym, jak drugi uznał (instrukcją <code>if</code>), że też może to zrobić.
  11.  
  12. Drugi typ synchronizacji to powiadamianie o zdarzeniach i to właśnie do niego stosują się wspomniane na początku metody klasy <code>Object</code>. Świadczą one o tym, że w Javie każdy obiekt może być czymś, co fachowo nazywa się <strong>monitorem</strong> - wysokopoziomową konstrukcją synchronizacyjną (wyższą niż np. znane i lubiane semafory). <code>synchronized</code> jest jedną z części tego mechanizmu, zapewniającą wyłączny dostęp. Drugą jest możliwość, aby wątki czekały (<code>wait</code>) na zdarzenia sygnalizowane przez inne wątki (<code>notify</code>), czego podręcznikowym przykładem jest generowanie i przetwarzanie danych porcjami - czyli scenariusz <strong>producent-konsument</strong>:
  13. [java]public class Transfer {
  14.     private object data = null;
  15.  
  16.     public synchronized void produce(object obj)
  17.         throws InterruptedException {
  18.         if (data != null) wait();
  19.         data = obj;
  20.         notify();
  21.     }
  22.  
  23.     public synchronized object consume()
  24.         throws InterruptedException {
  25.         if (data == null) wait();
  26.         object obj = data;
  27.         data = null;
  28.         notify();
  29.         return obj;
  30.     }
  31. }

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.

Tags: , ,
Author: Xion, posted under Programming »


2 comments for post “Podstawowa wielowątkowość w Javie”.
  1. KermiDT:
    December 18th, 2010 o 16:21

    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.

  2. Xion:
    December 18th, 2010 o 18:05

    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 :)

Comments are disabled.
 


© 2017 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.