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):
.................................................................
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, suspend i resume
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żą:
- obiekty z metodami synchronizowanymi.
- specjalna konstrukcja: synchronized ( obiekt ) { kod }
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
...
- Jeśli wątek A wywoła metodę synchronizowaną obiektu X to mówimy
że wątek A zajął
monitor obiektu X. Gdy metoda ta się
zakończy to mówimy że A zwolnił
monitor
obiektu X.
- W danej chwili tylko 1 wątek może zajmować
monitor danego obiektu. Tak więc metody synchronizowane zapewniają
wzajemne wykluczanie.
- Jeśli wątek A zajmuje monitor obiektu X i pewien wątek B spróbuje
wywołać metodę synchronizowaną obiektu X, to wtedy wątek B
zostanie zablokowany. Wątek B zostanie odblokowany
dopiero gdy A zwolni monitor obiektu X czyli gdy metoda synchronizowana
którą A uruchomił się zakończy.
Inny sposób aby zająć monitor obiektu X to wykonać taki kod:
- synchronized (X) { tu zajmuję monitor obiektu X }; tu już
nie zajmuje
Każdy obiekt w języku Java posiada metody wait() i notify()
odziedziczone po klasie Object:
- Wywołanie metody wait() na rzecz obiektu X powoduje
zwolnienie
monitora
obiektu X, oraz wstrzymanie wykonania wątku wywołującego wait().
- Wywołanie metody notify() na rzecz obiektu X powoduje wznowienie
jednego spośród wstrzymanych na tym obiekcie wątków, ale dopiero wtedy,
gdy wątek wywłujący notify() zwolni monitor obiektu.
- Metody X.wait(), X.notify() i X.notifyAll() mogą być
wykonywane jedynie przez wątki które zajmują monitor obiektu X.
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:
- metoda Thread.join();
oczekiwanie na zakończenie jakiegoś wątku - metoda
Object.notifyAll();
wznawianie wszystkich (a nie tylko jednego) wstrzymanych wątków
[różnica notifyAll()/notify() - patrz poniższe Zadanie]- grupy
wątków;
można wykonywać operacje na całej grupie wątków tak jak na pojedynczym
wątku- priorytety wątków; można je modyfikować, (podobno)
stosuje się tzw planowanie wielopoziomowe
[patrz poniższe Zadanie]
...............................................................................
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 ...
class Semafor {
private int wartosc;
private int licznik; // liczba wstrzymanych wątków ...
public Semafor (int _wartosc) {
wartosc = _wartosc;
licznik = 0;
}
synchronized public void signalSem() {
if ( licznik > 0 )
notify();
else
wartosc++;
}
synchronized public void waitSem() {
if ( wartosc > 0 )
wartosc--;
else {
licznik++;
try { wait(); } catch(InterruptedException e) {}
licznik--;
}
}
}
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():
- przez wywołanie notify() // wznawia jeden wątek
- przez wywołanie notifyAll() // wznawia wszystkie wątki
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:
- przez notify() // przypadek 1
- przez notifyAll() // przypadek 2
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().