JavaWyk01-Watki-MenBezp-Strum-TK.pdf

(410 KB) Pobierz
Microsoft Word - JavaWyk01-Watki-MenBezp-Strum-TK.doc
Wątki
Wątek jest pojedynczym sekwencyjnym strumieniem sterowania wewnątrz programu. Mówiąc o wątkach i
ich wzajemnych relacjach bierze się pod uwagę: kto je stworzył, kiedy powinny zostać uruchomione lub
zatrzymane, jaką funkcje realizują, etc.
Metody tworzenia wątków.
Istnieją dwie metody tworzenia wątków:
1.Utworzyć podklasę klasy Thread , przysłaniając metodę run własną metodą, a następnie utworzyć składnik tej
nowej podklasy i wywołać metodę run.
class A extends Thread {
public A(String name) {super(name);}
System.out.println(„My name is ” + getName());
}
}
class B {
public static void main(String[] args) {
A a = new A(„mud”);
a.start();
}
}
2. Zaimplementować interfejs (uruchomieniowy) Runnable w klasie z publiczną metodą run , stworzyć
składnik tej klasy, przekazać referencje do tego obiektu do konstruktora Thread. Zaletą jest tu możliwość
rozszerzenia ( extends ) klas zdefiniowanych przez użytkownika.
class C extends .... implements Runnable {
public void run() {
System.out.println(„Wątek o nazwie ” + Thread.currentThread().getName());
}
}
class B {
public static void main(String[] args) {
C c = new C();
Thread t = new Thread(c, „mud too”);
t.start();
}
}
Możliwe stany wątków:
tworzony (new), gotowy (runnable), wykonywany (running), zawieszony (suspended), zablokowany (blocked),
zawieszony-zablokowany (suspended-blocked), zakończony (dead). Przejście pomiędzy stanami możliwe jest
dzięki wywołaniu którejś z funkcji:
* yield, sleep (metody statyczne w klasie Thread, stosują się więc tylko do bieżąco wykonywanego wątku);
* resume, stop, suspend, start, join (metody składowe (instancyjne), które mogą wywołać dowolne wątki
na dowolnym obiekcie typu Thread );
* wait (może być bez parametrów i z parametrami: ( long milisec ) oraz ( long milisec, int nanosec )), notify ,
notifyAll (muszą być wywoływane z wnętrza bloku synchronized ).
wait(), wait(timeout) , wait(timeout,nanoseconds) –milisec,nanosec.
Tabela stanów wątków.
Current
New State
public void run() {
321247309.006.png 321247309.007.png
State
runnable
running
suspended blocked
suspended-
blocked
dead
new
start
runnable
scheduled suspend
stop
running
time slice
ends,
yield
suspend blocking
IO,
sleep ,
wait ,
join
stop ,
run ends
suspended resume
stop
blocked
IO
completes,
sleep
expires,
notify ,
notifyAll ,
join
completes
suspend stop
suspenden-
blocked
IO
completes
resume
stop
W Javie nowo tworzony wątek dziedziczy priorytet wątku, który go stworzył. Wątki obdarzane są priorytetami,
w zakresie od MIN_PRIORITY do MAX_PRIORITY (stałe zdefiniowane w klasie Thread , standardowo w
Javie priorytety mają wartości od 1 do 10, przy czym normalnie dla wątków 5). Standardowo wątkom
przyznawany jest priorytet NORM_PRIORITY , ale można też użyć setPriority, getPriority , aby go zmienić.
public class TestPriorytetow {
public static void main(String argv[]) {
C c = new C();
Thread t1 = new Thread(c, "pierwszy watek");
Thread t2 = new Thread(c, "drugi watek");
t2.setPriority(t1.getPriority()+1);
t1.start();
t2.start();
}
W każdej chwili, gdy wiele wątków gotowych jest do działania, runtime system wybiera ten wątek (runnable)
który ma najwyższy priorytet i go wykonuje. Tylko w przypadku, gdy działający wątek zatrzymuje się (po
stop ), oddaje sterowanie (po yield ), lub przestaje być wątkiem działającym (not runnable), wątek o niższym
priorytecie zacznie się wykonywać. W przypadku wątków o tym samym priorytecie, scheduler wybiera jeden z
nich na zasadzie round-robin. Wybrany wątek będzie działał dopóty, dopóki nie stanie się prawdziwe co
najmniej jedno ze zdań:
• Wątek o wyższym priorytecie staje się gotowym do wykonania (runnable).
• Bieżący wątek oddaje sterowanie (po yield ) lub jego metoda run się kończy.
• W systemie w podziałem czasu skończy się okres przydzielony dla wątku
Gdy któreś z powyższych zdań okaże się prawdziwe, wtedy zacznie działać wątek o niższym priorytecie.
Jednak reguła ta może okazać się fałszywa, gdy scheduler został tak zaimplementowany, aby nie doprowadzać
do zagłodzenia wątków o niskich priorytetach.
Uwaga: przy pracy z wątkami należy pamiętać o ich synchronizacji i zabezpieczeniu zmiennych
współdzielonych. Zobacz opis do słów: volatile , synchronized .
321247309.008.png
Podział czasu. Standardowo procesor przydzielany jest wątkom na okres 100 ms. Dzieje się tak jednak tylko dla
Windows95/NT. W przypadku JDK1.1 dla systemu Solaris 2.x nie było zaimplementowanego podziału czasu.
Aby sprawdzić, z jakim schedulerem ma się do czynienia, wystarczy uruchomić następujący przykład:
class C extends .... implements Runnable {
public void run() {
System.out.println(„Wątek o nazwie ” + Thread.currentThread().getName());
// aby zapewnic przełaczanie wątków niezależnie od schedulera można w tym
miejscu wywołać
// Thread.yield(); lub równoważnie Thread.currentThread().yield();
}
}
public class TestScheduleraWatkow {
public static void main (String argv[]) {
DzialajacyWatek dw = new DzialajacyWatek();
new Thread(dw, "pierwszy watek").start();
new Thread(dw, "drugi watek").start();
new Thread(dw, "trzeci watek").start();
}
}
Jeśli na ekranie wyświetlany będzie cały czas komentarz „Wątek o nazwie pierwszy wątek”, znaczy to, że nie
ma przełączania wątków. W takim przypadku można zaimplementować własny scheduler, który uruchamiany
będzie z najwyższym priorytetem i który będzie dokonywał przełączeń pomiędzy procesami wykorzystując
metodę nup() . Wątek taki powinien być uruchomiony przed wszystkimi innymi wątkami oraz powinien
wywoływać metodę setDeamon(true) .
public class Scheduller implements Runnable {
private int timeSlice = 0; // milliseconds
private Thread t = null;
public Scheduller(int timeSlice) {
this.timeSlice = timeSlice;
t = new Thread(this);
t.setPriority(Thread.MAX_PRIORITY);
}
public void run() {
int napping = timeSlice;
while (true) {
try{
t.sleep(napping);
} catch(InterruptedException e){}
}
}
}
Poniżej zamieszczony jest kolejny przykład na uruchamianie wątków. W przykładzie tym przechwytywany jest
wyjątek, wykorzystana jest metoda join() oraz currentThread() .
public class TestUruchamianiaWatkow {
public static void main (String argv[]) {
C c = new C();
while (true){
t.setDaemon(true); t.start();
321247309.009.png 321247309.001.png
Thread t = new Thread(c, “wątek pierwszy”);
System.out.println("operator new Thread() wykonany" + (t == null ? "blednie" ;
"poprawnie") + ".");
t.start();
try {
t.join(); // czeka aż wątek zakończy wykonywanie metody run()
}
catch (InterruptedException ignored){}
}
}
}
Jeden wątek może przerwać inny wątek przez wywołanie metody składowej interrupt przerywanego wątku.
Jeśli przerywany wątek jest w zablokowany po sleep , join lub wait , metody te wysyłają obiekt
InterruptedException zamiast się normalnie kończyć. Jeśli wątek nie jest zablokowany, to ustawia się po jego
przerwaniu odpowiednia logiczna flaga. Flagę można sprawdzić metodą składową isInterrupted (zwracającą
logiczną wartość). Dla wygody w klasie Thread zamieszczono statyczną metodę interrupted, która wywołuje
currentThread().isInterrupted() . Wywołanie to resetuje flagę (czego nie robi isInterrupted ).
Do sprawdzania „żywotności” wątków służą metody isDeamon , isAlive , getName. isAlive zwraca false dla
nowego wątku albo wątku zakończonego (dead thread), zwraca true dla wątków ruchomionych metodą start i
nie zatrzymanych (tj. wątków gotowych do działania lub jak i wątków niegotowych (runnable or not runnable),
przy czym nie można rozróżnić wątku nowego od skończonego jak również wątku gotowego od wątku
niegotowego. Ponadto do oceny stanu wątków można posłużyć się mechanizmem wyjątków. W poniższym
przykładzie przechwytywany jest wyjątek ThreadDeath.
public class TestZatrzymania {
public static void main(String argv[]) {
Thread t = new Thread(new DzialajacyWatek());
try {
t.start();
metodaMogacaZatrzymacWatek();
} catch (ThreadDeath aTD) {
// tu jest miejsce na zareagowanie na przerwanie watku
throw aTD; // przekaz blad dalej
}
}
Wątki można grupować w pewne zbiory. Do tworzenia zbiorów wyjątków służy klasa ThreadGroup .
Dostęp do pamięci. Zgodnie z definicją języka Java, wpisywanie danych do komórek pamięci nie musi
odbywać się w kolejności, w jakiej zadeklarowane to zostało w programie. Aby taką kolejność zachować,
deklaruje się nadpisywaną przez wątki zmienną słowem volatile
class D {
static int x = 0, y = 0;
static void a() {x = 3, y = 4; }
static int b() {int z = y; z += x; return z;}
}
Jeśli różne wątki wywołają a() i b(), to zwracane wartości przez b mogą być następujące: 0, 3, 4, 7 (gdzie 4 jest
zwrócone, jeśli a zapisze w y wartość 4 i będzie to widoczne dla b wcześniej niż zapis wartości 3 w x . Jeśli
obie zmienne byłyby zadeklarowane jako
static volatile int x =0, y =0;
321247309.002.png
to b nigdy nie zwróciłoby 4.
Aktywne czekanie:
sensownie
raczej źle
while (buffer[putIn].occupied)
Thread.currentThread().yield();
while (buffer[putIn].occupied);
Można też aktywne czekanie zaimplementować następująco
while (condition) try {wait();} catch (InterruptedException e) {}
i używać notifyAll .
Semafory. W standardzie Javy nie wyróżniono semaforów. Dlatego trzeba je symulować przy użyciu
monitorów (które mieszczą się już w standardzie tego języka). Pewną „namiastką” semaforów binarnych jest
blok synchronizacji, służący zabezpieczaniu sekcji krytycznej.
Przykład
synchronized (obj) { critical section }
obj może tu być dowolnym obiektem.
Jeśli wszystkie sekcje krytyczne znajdują się w metodach pojedynczego obiektu, wtedy blok synchronizacji
używa this (referencje do tego obiektu)
synchronized (this) { ... }
Ponadto, jeśli ciało metody stanowi blok synchronizacji z this , tj.
type method ( ... ) { synchronized (this) { critical section } }
kompilator Javy pozwala na użycie zapisu
synchronized type method ( ... ) { critical section }
Jeśli wzajemne wykluczanie dotyczy wielu różnych klas bądź obiektów, wtedy obiekt wspólny dla
synchronizowanych bloków musi zostać stworzony na zewnątrz klasy i musi być przekazany do ich
konstruktorów.
private Object mutex = null;
....
mutex = this ;
....
public void run(){
for ( int m = 1; m <= M; m++)
synchronized (mutex) { sum = fn (sum, m); } }
mutex traktowany jest tu jako
zmienna warunkowa, na której
dokonuje się podnoszenia i
opuszczania.
Uwaga: semafory binarne można używać do wzajemnego wykluczania jak i do synchronizacji procesów.
Użycie bloku synchronizacji umożliwia tylko to pierwsze.
synchronized – zabezpiecza przed jednoczesnym wykonaniem fragmentu kodu zawierającego dany obiekt
przez współbieżnie działające wątki.
// zabezpieczenie przypisania w metodzie print względem obiektu TryPointPrinter
public class TryPointPrinter {
public void print (Point p) {
float safeX, safeY;
synchronized(this) {
safeX = p.x();
safeY = p.y();
}
System.out.println("x =", + safeX + ", y=" + safeY );
}
}
321247309.003.png 321247309.004.png 321247309.005.png
Zgłoś jeśli naruszono regulamin