Model współbieżności w Javie Języki i techniki programowania Marcin Rusek
Współbieżność w Javie Współbieżność – jednoczesne wykonywanie komunikujących się i współpracujących ze sobą wątków (ang. threads) Uzyskiwana na komputerach wieloprocesorowych – na jednoprocesorowych jedynie emulowana.
Wątek każdy ma wirtualny procesor (programuje się wątek tak, aby działał samodzielnie ) kod i dane są często współdzielone między wątkami pracą wątków steruje planista wątków (thread scheduler)
Tworzenie wątków w Javie Program wielowątkowy definiujemy na dwa sposoby: jako podklasę klasy java.lang.Thread implementując intefejs Runnable
Dziedziczenie po klasie Thread public class MojWatek extends Thread { int ID; public MojWatek( int id ) { ID = id; } public void run() { System.out.println( „Mój Numer to" + ID ); public class WatekTest { public static void main( String [] args ) { MojWatek t1 = new MojWatek( 1 ); MojWatek t2 = new MojWatek ( 2 ); t1.start(); t2.start();
Implementacja interfejsu Runnable public class MojaRunnable implements Runnable { int ID; public MojaRunnable( int id ) { ID = id; } public void run() { System.out.println( "Moj mumer to " + ID ); public class RunnableTest public static void main( String [] args ) { MojaRunnable r1 = new MojaRunnable( 1 ); Thread t1 = new Thread( r1 ); t1.start(); Thread t2 = new Thread(new MojaRunnable(2)); t2.start();
Porównanie tworzenie wątku za pomocą implementacji interfejsu Runnable daje większe możliwości, gdyż w Javie można dziedziczyć tylko po jednej klasie każda instancja klasy implementującej interfejs Runnable jest powiązana z wątkiem w czasie wykonania(ang. runtime); wątek używa metody run() tej klasy zamiast metody run() klasy Thread, jak to ma miejsce, gdy tworzymy wątek używając klasy pochodnej od klasy Thread
run() Wszystkie zadania, jakie ma wykonywać wątek umieszczone są w metodzie run klasy implementującej interfejs Runnable bądź klasy dziedziczącej po klasie Thread. Po utworzeniu i inicjalizacji wątku, środowisko przetwarzania wywołuje metodę run.
Stany wątków cd. nowy – stworzony, ale przed uruchomieniem metody start() uruchamialny (ang. runnable) – po wywołaniu metody start(), czeka na zlecenie od planisty uśmiercony – po zakończeniu metody run() lub po wywołaniu jednej z metod: stop(), destroy() zablokowany – mógłby działać, ale istnieją jakieś przeciwwskazania ku temu ( np. może czekać na operacje we/wy )
Stany wątków Uśmiercony Nowy
Kontrola wątków
start() Nowo zainicjowany wątek NIE startuje automatycznie.Musimy wykonać na nim metodę public void start() Metoda start() powoduje, że wątek przechodzi w stan „uruchamialny” (runnable) – czeka gotowy na polecenie od planisty wątków (ang. threads scheduler)
Wywłaszczanie wątków Istnieją metody zmuszające wątek do zrzeczenia się kontroli procesora: sleep() yield() Obie wywoływane są na rzecz wątka, którego chcemy wywłaszczyć
sleep() public static void sleep(int ms) throws InterruptedException przerywa realizację wątku na określoną liczbę milisekund chyba, że zostanie wywołana na jego rzecz metoda interrupt() po obudzeniu wątek powraca do stanu „uruchamialny”
yield() public static void yield() dobrowolne oddanie kontroli nad procesorem do innych wątków mających priorytet wyższy lub równy wątek natychmiast powraca do stanu „uruchamialny”
Inne metody związane z wątkami Wywoływane przez jeden wątek do monitorowania lub reagowania na stan innego wątku isAlive() – sygnalizuje czy wątek, mający się wykonywać, zakończył się ( true – wątek jest żywy tzn. wystartował, ale jeszcze nie „umarł”; w przeciwnym wypadku false) join() – czeka dopóki wątek bieżący się nie zakończy się
Inne metody związane z wątkami cd. interrupt()- przerywa wątek na rzecz którego jest wywołana do przerwanego obiektu zgłasza wyjątek InterruptedException wpływa na zachowanie wątku w przypadku, gdy zostały wywołane jego metody: wait(), join(), sleep()
Priorytet informuje planistę jak ważny jest dany wątek posiada go każdy wątek nadawany jest w czasie tworzenia wątków ( z reguły wątek dziedziczy priorytet z wątku, który go utworzył ) można ustawić go programowo getPriority() setPriority() Thread.MIN_PRIORITY, Thread.NORM_PRIORITY (domyślna), Thread.MAX_PRIORITY
Planista wątków (ang. thread scheduler) decyduje, który wątek ma się wykonać jako następny decyduje, kiedy zastąpić wykonywany wątek innym wątkiem (musi on być w stanie „uruchamialny”)
Jak podejmuje decyzję planista? W większości przypadków planista: Wybiera wątek z najwyższym priorytetem Pozwala wykonywać się wątkowi, dopóki się nie zakończy, nie przejdzie w stan „zablokowany”( czekając np. na jakieś zasoby) lub dopóki wątek o wyższym priorytecie nie przejdzie do stanu „uruchamialny”
Wątki demony zajmuje się obsługiwaniem innych wątków uruchomionych w tym samym procesie, co wątek demona w swojej metodzie run() czeka na zgłoszenia zapotrzebowania na usługi dostarczane przez ten wątek wykonanie programu kończy się z chwilą zakończenia ostatniego wątku, który nie jest demonem public final void setDaemon(boolean a)– ustawia wątek jako demon, gdy argument równy true; wywoływana przed metodą start() public final boolean isDeamon()- sprawdza czy wątek jest demonem
Synchronizacja uwagi ogólne Gdy w programie wiele wątków ma dostęp do wspólnych danych, to musi być zaimplementowany jakiś mechanizm blokujący
Synchronizacja cd. Java dostarcza wbudowane mechanizmy zapobiegające kolizjom przy dostępie do danych przez wiele wątków. Synchronizacja metod: - metodę synchronizowaną w tym samym czasie może wywołać tylko jeden wątek (nie jest możliwe wykonanie równocześnie więcej niż jednej metody zsynchronizowanej na rzecz tego samego konkretnego obiektu) - dopuszczalne są równoczesne wywołania przez wątki metod synchronizowanych różnych obiektów
Synchronizacja cd. Mechanizm blokowania się synchronizowanych metod polega na ustawianiu w konkretnym obiekcie blokady, który staje się automatycznie częścią obiektu. Przy każdym wywołaniu synchronizowanej metody blokada jest ustawiana, a na zakończenie metody jest zwalniana. Wtedy, gdy obiekt jest zablokowany, niemożliwe jest wywołanie innej zsynchronizowanej metody na rzecz tego obiektu
Synchronizacja cd. Do synchronizacji używamy słowa kluczowego synchronized , np. public synchronized void func(){ ... }
Synchronizacja cd. Synchronizacja bloku kodu Public void func(){ ... synchronized(obiekt){.... } } dostęp do zsynchronizowanego bloku ma tylko jeden wątek na raz (następuje blokada) wszystkie inne wątki, które chcą uzyskać dostęp do zsynchronizowanej metody (czy bloku kodu), a blokada jest już założona, przyłączają się do puli wątków czekających
Synchronizacja cd. Java zapewnia, że blokada jest automatycznie zwracana po wyjściu wątku z synchronizowanego bloku nawet gdyby kontrolę przejął wyjątek lub instrukcje break czy return
Komunikacja miedzy wątkami Odbywa się za pomocą trzech funkcji wait() – wstrzymuje wątek i zmusza go do czekania, aż nie zostanie wywołana metoda notify() lub notifyAll() notify() – budzi jeden z oczekujących wątków notifyAll() – budzi wszystkie oczekujące wątki Powyższe funkcje muszą należeć do bloków lub metod zsynchronizowanych. NIE należy używać funkcji sleep() i yield(), gdyż NIE zwalniają blokady obiektu => zakleszczenia!!
Problem konsument-producent Przykład Problem konsument-producent public class Zasoby { private int ilosc ; public synchronized void zwieksz() { while ( ilosc == 1 ) { try { wait(); } catch (InterruptedException e) { ; } } ilosc++ ; notifyAll() ; public synchronized void zmniejsz() { while ( ilosc == 0 ) { try { wait(); } catch (InterruptedException e) { ; } ilosc-- ;
Przykład cd. public class Konsument implements Runnable { private Zasoby zasoby ; public Komnsument( Zasoby zasoby ) { this.zasoby = zasoby ; } public void run() { zasoby.zmniejsz() ; public class Producent implements Runnable { private Zasoby zasoby ; public Producent(Zasoby zasoby ) { this.zasoby = zasoby; } public void run() { zasoby.zwieksz() ;
Zakleszczenia
Zakleszczenia (ang. deadlocks) sytuacja w programie, gdy ( przynajmniej dwa) wątki będąc wstrzymane czekają na zwolnienie blokady pojawiają się, gdy kilka wątków rywalizuje o dostęp do zbiorowych danych, np. jeden wątek czeka na blokadę trzymaną przez drugi wątek podczas, gdy drugi czeka na blokadę trzymaną przez pierwszy wątek Java nie wykrywa sytuacji zakleszczeń, więc PROGRAMISTA musi sam zadbać o ich wyeliminowanie
Zakleszczenia cd. Mogą wystąpić, gdy spełnione są cztery następujące warunki wzajemne wykluczenie – przynajmniej 1 zasób wykorzystywany przez wątki musi nie być współdzielony przynajmniej 1 proces musi posiadać zasób i oczekiwać na uzyskanie innego, aktualnie w rękach innego procesu wszystkie procesy muszą zwalniać zasoby w normalny sposób tzn. nie może mieć miejsca sytuacja, w której jeden proces wywłaszcza drugiego z zasobów zapętlone oczekiwanie
Ostateczny diagram aktywności dla wątku Uśmiercony Nowy
Niezalecane metody związane z wątkami Poniższe funkcje NIE zwalniają blokad obiektów suspend() – zawiesza wykonanie wątku resume() – wznawia zawieszony wątek stop() – wymusza na wątku natychmiastowe zatrzymanie ( może przerwać program w stanie niespójnym); generuje wyjątek ThreadDeath destroy() –uśmierca wątek, ale nie zwalnia blokad obiektów
Bibliografia Sun Web Learning Center Courses http://learningcenter-sai.sun.com Bruce Eckel „Thinking in Java” 3rd edition JavaTM 2 Platform, Standard Edition, v 1.3.1 API Specification http://java.sun.com/j2se/1.3/docs/api/ http://www.javasoft.pl/