SOP322/ćw - temat F

Współbieżność w języku Java.


Help "MS Windows" z opisem klas Java 1: Jdk102.hlp.

Dokumentacja "Java 2":
    http://java.sun.com/j2se/1.4.2/docs/index.html
    opis klasy Thread http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Thread.html
    opis klasy Object http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Object.html

Literatura: 
    J. Bielecki, "Java po C++", str 143-160
    A. Holub, "Wątki w Javie, poradnik dla programistów"
    Bruce Eckel "Thinking in Java" http://binboy.sphere.pl/index.php?show=serwis&d=tijava (online)
            http://www.webhost-galaxy.com/mirrors/eckelbooks/ (download)
            (także inne książki tego autora są tam dostępne, np "Thinking in C++", "Thinking in Enterprise Java")
 

1. Tworzenie wątków.

  • Każdy wątek w języku Java wymaga istnienia obiektu klasy Thread, sterującego tym wątkiem.
  • Konstruktor klasy Thread wymaga odniesienia do obiektu klasy implementującej interfejs Runnable, zawierającej metodę run(), której kod będzie wykonywany przez wątek.
  • Aby uruchomić wątek, należy wywołać metodę start(), klasy Thread.  Aby zniszczyć wątek należy wywołać metodę stop() [przestarzała !].  Do zawieszania i odwieszania wątków (terminologia J.B.), służą metody suspend() i resume().  Do uśpienia wątku na określony czas służy metoda sleep().  Zauważmy, że automatycznie zdefiniowaliśmy stany, w jakich może się znajdować wątek ... 
    Uwaga: Nie w każdym stanie wątku można wywołać każdą z wymienionych metod (jeśli wywołamy sleep() gdy wątek jest w stanie zawieszenia to zostanie wygenerowany wyjątek klasy InterruptedException !).
  • Stany wątku w Javie (nowy, wykonywany, nie wykonywany, zakończony):
    stany wątku

    .................................................................

    Przypomnijmy sposób kompilowania i uruchamiania programów w języku Java:

        javac Watek01.java
          # kompilacja
        java Watek01
          # wykonanie programu
    Oto najprostszy przyklad "Watek01.java". W przykładzie tym wątek główny tworzy 3 wątki, które wyświetlają swoją nazwę (AAA, BBB, CCC) wraz z kolejną liczbą.

    W przykładzie "Watek02.java" główny wątek steruje pozostałymi trzema wątkami (wyłącza je po upływie określonego czasu).

    Zadanie 80


    Zmodyfikuj przykład "watek02.java", w taki sposób, aby watek główny zawieszał i odwieszał pozostałe 3 watki, co 200 milisekund (w danej chwili ma działać 1 z tych 3 wątków). Użyj funkcji suspend() i resume() klasy Thread.
     

    UWAGA:
    Dlaczego przykład "Watek02.java" nie działał prawidłowo ?
    Okazuje się, że metody stop, suspendresume są przestarzałe, gdyż ich implementacja jest "niebezpieczna", o czym można się przekonać czytając dokumentację "Java 2" przytoczoną poniżej:
     
    stop 

    public final void stop() 

    Deprecated. This method is inherently unsafe. Stopping a thread with Thread.stop causes it to unlock all of the monitors that it has locked (as a natural consequence of the unchecked ThreadDeath exception propagating up the stack). If any of the objects previously protected by these monitors were in an inconsistent state, the damaged objects become visible to other threads, potentially resulting in arbitrary behavior. Many uses of stop should be replaced by code that simply modifies some variable to indicate that the target thread should stop running. The target thread should check this variable regularly, and return from its run method in an orderly fashion if the variable indicates that it is to stop running. If the target thread waits for long periods (on a condition variable, for example), the interrupt method should be used to interrupt the wait. For more information, see Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?. 

    Forces the thread to stop executing.

     
     

    2. Synchronizowanie wątków.

    Do synchronizacji wątków służą:
    class Bank
    {
    private long konto1=123456, konto2=0;

    // przykład metody synchronizowanej:
    synchronized public void Przelew(long kwota)
    {
    konto1-=kwota; konto2+=kwota;
    }
    }

    Metoda "synchronizowana" to taka, której definicja jest poprzedzona słowem synchronized ...

    Inny sposób aby zająć monitor obiektu X to wykonać taki kod:

    Każdy obiekt w języku Java posiada metody wait() i notify() odziedziczone po klasie Object:

    UWAGA 1:  
    Jak widać każdy obiekt języka Java jest monitorem z jedną zmienną "condition". (Porównanie z terminologią monitorową języka BACI: wątki zablokowane = procesy wstrzymane w kolejce procesów zewnętrznych; wątki wstrzymane= procesy śpiące na zmiennej condition).

    UWAGA 2:  
    Metoda notify() wznawia jeden wątek wstrzymany na wait() jednak NIE oznacza to, że wątek ten zaraz zacznie się wykonywać i wejdzie ponownie do monitora - mogą go w tym uprzedzić inne wątki - nasz obudzony wątek współzawodniczy ze wszystkimi innymi wątkami próbującymi zająć monitor.

    UWAGA 3:  
    NotifyAll() tym się różni od notify() że budzi wszystkie wątki wstrzymane na wait() - jednak monitor może zająć tylko jeden wątek!

    Opisy metod klasy Object z dokumentacji "Java 2":
        notify() http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Object.html#notify()
        notifyAll() http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Object.html#notifyAll()
        wait() http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Object.html#wait()


    ..................................................................................

    Ćwiczenie 81


    Oto najprostszy przykład synchronizacji wątków "Watek03.java". W przykładzie tym wątki dokonują przelewów między zmiennymi konto1 i konto2.  Wszystkie wątki działają "w nieskończoność", dlatego należy ten program kończyć przez Ctrl-C.  Sprawdź, co się stanie po wzięciu w komentarz słowa "synchronized", w klasie Bank03.

    Zadanie 82


    Oto przykładowe rozwiązanie problemu producenta/konsumenta w wersji z nieskończonym buforem: Watek04.java. Rozwiązanie to nie działa (widać to dla większej od 1 liczby konsumentów). Zastanów się dlaczego i popraw błąd.
    Wskazówka: w monitorach j. BACI mieliśmy zagwarantowane, że jeśli proces P "śpi w oczekiwaniu na spełnienie warunku W", tj znajduje się w kolejce uśpionych procesów zmiennej C typu condition, to wtedy jeśli inny proces Q wykona instrukcje signalc(C), to pociągnie to za sobą natychmiastowe wznowienie procesu P, natomiast proces Q zostanie wstrzymany i znajdzie się na początku kolejki wejściowej monitora.  Tak więc jeśli przed wykonaniem signalc(C) był spełniony warunek W, to po wznowieniu procesu P będzie on nadal spełniony.  Niestety, takiej gwarancji nie mamy w języku Java: po wykonaniu notify() nie ma gwarancji że natychmiast zostanie wznowiony jakiś watek wstrzymany przez wait() - zamiast tego nowy wątek może zająć monitor i zmodyfikować wartość zmiennych (z tego powodu warunek spełniony przed notify() nie musi być spełniony po wznowieniu uśpionego wątku).

    ...............................................................................

    Nie poruszyliśmy jak dotąd następujących tematów, związanych ze współbieżnością w Javie:

     ...............................................................................
     

    Zadanie 84 (*)


    Zaimplementuj klasę Semafor z metodami waitSem() i signalSem() realizującą semafor ogólny w języku Java.
  • Zastanów się czy poniższa implementacja jest poprawna, jeśli nie to napisz dlaczego ...
  • Sprawdź doświadczalnie poprawność implementacji Twojego semafora.  Napisz program tworzący 10 wątków (0..9) mających dostęp do semafora o wartości początkowej 4.  Gdy wątek o numerze "x" wchodzi do sekcji chronionej tym semaforem, to ustawia na 1 element pewnej tablicy o indeksie "x", gdy wychodzi z sekcji to zeruje ten element.  Powinien istnieć wątek kontrolny pokazujący zawartość wspomnianej tablicy.
  • Zadanie 85


    W przykładzie "Watek05.java" mamy dwa rozwiązania (Bufor05_1 i Bufor05_2) problemu producenta i konsumenta ze skończonym buforem.  Sprawdź czy są one poprawne. Jeśli któreś z rozwiązań nie jest poprawne to napisz dlaczego tak jest!  Zastanów się jak - ogólnie - rozwiązywać problemy wymagające monitora z liczbą zmiennych condition >1?  

    Zadanie 86


    Podaj w języku Java rozwiązanie problemu czytelników i pisarzy przy założeniu że rozmiar czytelni NIE jest znany.
    Założenie: nie wolno używać "własnego" semafora z jednego z poprzednich zadań!

    Zadanie 88


    [Jaka jest różnica między notify() a notifyAll() ?]
    Wykonaj eksperyment pokazujący jaka jest różnica między dwoma sposobami budzenia wątków wstrzymanych podczas wykonywania wait():

    Pozornie wydaje się że nie ma żadnej różnicy ponieważ nawet jeśli notifyAll() wznawia WSZYSTKIE wątki to tylko JEDEN z nich może zająć monitor i kontynuować działanie ...
    Wskazówki:
    Utwórz 10 wątków o numerach 0..9, które będą zajmowały monitor "z zewnątrz", a więc w następujący sposób

  • public void run()
    {
    int i=1;
    while(true) {
    synchronized(mon) {
    System.out.println(nr+":"+(i++));
    try { Thread.currentThread().sleep(100); } catch(InterruptedException e) {}
    }
    }
    }
  • Utwórz 10 wątków o numerach 100..109, które będą opuszczały monitor w wyniku "wstrzymania na zmiennej condition" (używając terminologii języka BACI); wątki te powinny wykonywać następujący kod:

  • public void run()
    {
    int i=1;
    synchronized(mon) {
    while(true) {
    System.out.println(nr+":"+(i++));
    try { Thread.currentThread().sleep(100); } catch(InterruptedException e) {}
    try { mon.wait(); } catch(InterruptedException e) {}
    }
    }
    }
  • Wątek główny (wykonujący main()) powinien w nieskończonej pętli wznawiać procesy 100..109 przy pomocy:

    Sprawdź doświadczalnie co można powiedzieć o szybkości działania wątków w obu powyższych przypadkach.
    Uzasadnij uzyskane rezultaty; pamiętaj że jeśli kilka wątków równocześnie chce zająć monitor to wybiera się "sprawiedliwie" jeden z tych wątków ...
    .................................................................
    Opis metody notify() http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Object.html#notify().
    Opis metody notifyAll() http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Object.html#notifyAll().
     

    Zadanie 89


    [Modyfikowanie priorytetów wątków; metoda Thread.setPriority]
    Według opisu modelu współbieżności w Javie (rozdział "Priorytet wątku") planowanie przydziału procesora do wątków to tzw "planowanie wielopoziomowe", tj spośród wątków gotowych do działania ZAWSZE wybiera się wątki z najwyższym priorytetem. Oznacza to że jeśli mamy wątki A i B z priorytetami 2(wysoki) i 1(niski) to wątek B w ogóle nie będzie działał, chyba że wątek A zostanie wstrzymany/zablokowany. Zaprojektuj eksperyment który wykaże czy to prawda ...
    .................................................................
    Opis metody setPriority() http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Thread.html#setPriorityl().