• Nie Znaleziono Wyników

Wątki

N/A
N/A
Protected

Academic year: 2021

Share "Wątki"

Copied!
17
0
0

Pełen tekst

(1)

Programowanie

wielowątkowe

(2)

Wątki a procesy

Jako jeden z niewielu języków programowania Java udostępnia użytkownikowi mechanizmy wspierające programowanie

wielowątkowe. Program wielowątkowy zawiera kilka równoległych ścieżek wykonania. Każda tak ścieżka nazywa się wątkiem.

Wielowątkowość jest specyficzną formą wielozadaniowości. Istnieją dwa typy wielozadaniowości:

Bazujący na procesach: pozwala na równoległe wykonanie kilku programów

Bazujący na wątkach: pojedynczy program może wykonywać kilka zadań równolegle

Typ procesowy występuje we większości systemów operacyjnych. Proces posiada bogate środowisko wykonania, swoją przestrzeń adresową itp. Komunikacja i przełączanie między procesami są kosztowne.

Wątki są tańsze, ponieważ bazują na jednym procesie i współdzielą jego zasoby. Pozwalają maksymalnie wykorzystać procesor.

(3)

Cykl życia wątku

new Thread Wątek działa działa czeka wyłączony start sleep wait Koniec metody run Powyższy diagram przedstawia cykl życia wątku. Kodowanie

poszczególnych etapów życia wątku przedstawimy w dalszej części wykładu.

(4)

Wątki

priorytety

Java z każdym wątkiem programu kojarzy priorytet, informujący o tym jak należy traktować wątek w stosunku do innych wątków programu. Priorytet jest liczbą całkowitą określającą relatywną ważność wątku w stosunku do innych wątków.

Priorytet wątku nie ma wpływu na szybkość wykonywania wątku, a jedynie na pierwszeństwo w wyborze do wykonania (zmiany

kontekstu wykoniania). Reguły rządzące tym kiedy zmieniać kontekst wykoniania są następujące:

 Wątek może dobrowolnie odstąpić prawo do wykonania innym wątkom. Sytuacja taka ma miejsce, gdy wątek musi czekać na

zwolnienie zasobu lub, gdy wykonał metodę sleep. Wybierany jest wówczas wątek z oczekujących o najwyższym priorytecie.

 Wątek może zostać wyparty przez wątek o wyższym priorytecie.

Tj., gdy tylko wątek o wyższym priorytecie potrzebuje procesor, to go bierze.

(5)

Wątek główny

Wraz z uruchomieniem programu napisanego w Java jeden wątek, zwany wątkiem głównym, startuje natychmiast. Wątek główny jest ważny, ponieważ:

 Jest wątkiem mogącym zapoczątkowywać inne wątki, zwane wątkami potomnymi

 Często musi być ostatnim wątkiem kończącym wykonanie programu Wątek główny niczym nie różni się od innych wątków programu.

By uzyskać nad nim kontrolę musimy utworzyć do niego referencję, korzystając z publicznej statycznej metody currentThread() klasy Thread. Metoda ta oddaje referencję do wątku, w którym została

wywołana.

(6)

Tworzenie własnego wątku

By utworzyć nowy wątek trzeba utworzyć obiekt typu Thread. Można to zrobić na dwa sposoby:

Zaimplementować interfejs Runnable Rozszerzyć klasę Thread

Implementacja Runnable

Interfejs Runnable jest abstrakcją wykonywalnego kodu. Można utworzyć wątek bazując na dowolnym obiekcie

implementującym ten interfejs. By implementować ten interfejs, klasa musi jedynie implementować pojedynczą metodę:

public void run()

Ciało tej metody stanowi kod wątku. Wątek taki kończy się wraz z zakończeniem działania metody run(). Wywołanie konstruktora:

Thread(Runnable obWątku, String nazwa);

Tworzy nowy wątek bazując na obiekcie obWątku z nazwą nazwa. Metoda start() uruchamia metodę run() obiektu wątku.

Zobacz:

(7)

Rozszerzanie klasy Thread

Kolejnym sposobem tworzenia własnych wątków jest definiowanie

nowych klas dziedziczących po klasie Thread, następnie tworzenie ich instancji. Klasa rozszerzająca musi nadpisać metodę run() klasy

Thread. Podobnie jak w poprzednim przykładzie musimy wykonać metodę start() by rozpocząć wykonanie nowego wątku.

Zobacz: ExtendThreadDemo.java

Co lepsze?

Przedstawiliśmy dwa sposoby tworzenia nowych wątków. Który z nich jest lepszy? Zgodnie z metodologią obiektową dziedziczenie klas ma sens, gdy klasa pochodna zmienia coś istotnego w klasie

dziedziczonej. W naszym przypadku była to tylko metoda run(). Wydaje się, że w takim przypadku bardziej odpowiednia jest

implementacja interfejsu Runnable.

Powyższe rozważania traktują raczej o dobrym stylu, niż o poprawności kodowania.

(8)

Metody isAlive() i join()

W poprzednich programach staraliśmy się nie doprowadzać do sytuacji, w której wątek główny kończył by się zanim zakończą się wątki potomne. Efekt ten osiągaliśmy przez odpowiednio długie usypianie wątku głównego. Takie rozwiązanie jest rzadko

satysfakcjonujące w realnych programach.

Skąd więc wątek może wiedzieć, że inny wątek się zakończył? Odpowiedzi na to pytanie mogą udzielić dwie metody klasy Thread:

final boolean isAlive() oddająca true, gdy wątek, dla którego ją wywołujemy działa, false w przeciwnym przypadku

final void join() throws InterruptedException metoda

czeka, aż wątek, dla którego wywołujemy zakończy się; dodatkowe wersje join pozwalają podać maksymalny czas oczekiwania na zakończenie wątku

(9)

Priorytety wątków

W praktyce wątki o wyższym priorytecie powinny mieć łatwiejszy dostęp do procesora niż wątki i niższym priorytecie. W rzeczywistości dostęp do procesora zależy od wielu czynników. Najważniejszym z nich jest implementacja w danym systemie wielozadaniowości. Aby zapewnić wszystkim wątkom dostęp do procesora, także w środowiskach bez wywałaszczania, wątki powinny oddawać sterowanie co jakiś czas. Do ustawiania priorytetu wątku służy metoda:

final void setPriority(int poziom)

Parametr poziom, to nowy priorytet wątku. Jego wartości powinny być z zakresu od MIN_PRIORITY (1) do MAX_PRIORITY (10). Domyślnym priorytetem jest NORM_PRIORITY (5).

Kompilator optymalizując kod zakłada, że wykonanie bieżącego kodu zależy jedynie od niego samego. W przypadku wielu wątków często wątki mogą wpływać na siebie. Słowo volatile wstrzymuje

Zobacz:

(10)

Synchronizacja

koncepcje

Gdy dwa lub więcej wątków chce korzystać ze wspólnych zasobów, potrzebują pewnego sposobu upewnienia się, że z zasobu będzie

korzystać tylko jeden wątek w jednym czasie. W informatyce taki sposób upewniania się nazywa się synchronizacją. Zwykle synchronizacja

wymaga wykonania pewnego protokołu przez wątki. Java dostarcza własny sposób synchronizacji na poziomie języka.

Synchronizacja jest zapewniana w Java przez tzw. monitory.

Monitor jest obiektem zapewniającym wzajemne wykluczanie wątków. Tylko jeden wątek może być w posiadaniu jednego monitora w jednym czasie.

Wątek wchodzący do tzw. sekcji krytycznej otwiera monitor. Wszystkie inne wątki próbujące otworzyć ten sam monitor są

wstrzymane do czasu aż pierwszy wątek opuści monitor. Wątki takie nazywamy oczekującymi na dostęp.

(11)

Synchronizacja

przykład

Synchronizacja w Java nie jest zbyt skomplikowana. Każdy obiekt w Java posiada związany z nim niejawny obiekt monitora. Aby otworzyć monitor obiektu trzeba wywołać jego metodę zdefiniowaną z

modyfikatorem synchronized. Dopóki wątek znajduje się wewnątrz

metody synchronizowanej każdy inny wątek chcący wywołać tę lub

inną metodę synchronizowaną obiektu, musi czekać aż pierwszy wątek opuści metodę synchronizowaną.

Aby lepiej zrozumieć działanie monitorów w Java, prześledźmy przykład programu, w którym wątki mogą dostać się do sekcji krytycznej bez

synchronizacji.

Zobacz: NoSynch.java

Poprawka w poprzednim programie polega na synchronizowaniu

metody call() klasy CallMe. Po poprawce tylko jeden wątek będzie mógł korzystać z metody call() w jednym czasie.

(12)

Komenda synchronized

Użycie metod synchronizowanych jest prostym sposobem uzyskania wzajemnego wykluczania wątków. Niestety nie zawsze może być

stosowany.

Wyobraźmy sobie, że chcemy synchronizować dostęp wątków do

obiektu, który nie był zaprojektowany do używania przez wiele wątków. Tj. klasa ta nie używa metod synchronizowanych. Co więcej klasa ta nie była projektowana przez nas i nie mamy dostępu do kodu źródłowego. W jaki sposób możemy synchronizować dostęp do tego obiektu?

Odpowiedź:

Wywołania metod, które powinny być synchronizowane wstawiamy do bloku synchronized:

synchronized (obiekt) {

// synchronizowane komendy }

Powyżej obiekt jest synchronizowanym obiektem. Wywołania metod synchronizowanego obiektu będą możliwe tylko wtedy, gdy obiekt zdoła

Zobacz:

(13)

Wątki

komunikacja

Dotychczas wątki jedynie blokowały dostęp do zasobów krytycznych. Po zwolnieniu zasobu wątki konkurują o zwoniony zasób. Polegając na tym mechaniźmie nie możemy wyrazić bardziej subtelnych związków między wątkami, takich jak: uczciwość, żywotność, ... .

Java prócz mechanizmów zapewniających wzajemne wykluczanie

oferuje mechanizm wymiany sygnałów między współbieżnymi wątkami. Służą do tego metody finalne klasy Object: wait(), notify() i

notifyAll(). Metody te mogą być wywołane tylko z metody synchronizowanej. Ich znaczenie:

wait() informuje wątek wywołujący by opuścił monitor i zasnął do czasu aż inny wątek otworzy ten sam monitor i wywoła metodę

notify() lub notifyAll()

notify() budzi wątek, który jako pierwszy wywołał wait() na tym samym obiekcie

notifyAll() budzi wszystkie wątki, które wywołały wait() na tym samym obiekcie; wątek o najwyższym priorytecie działa, reszta śpi

(14)

Wątki

producent konsument

Produkcja Bufor Konsumpcja

Producent Konsument

Implementacja powyższego modelu producenta i konsumenta z

buforem jednoelementowym wymaga użycia mechanizmu sygnałów. Niepoprawny sposób implementacji zapewniający wyłącznie wzajemne wykluczanie w dostępie do bufora można znaleźć w pliku:

ProdKonsErr.java

Poprawną implementację można znaleźć w pliku:

ProdKons.java

(15)

Wątki

zakleszczenie

Istnieje specjalny typ błędu związanego z wielozadaniowością i wielowątkowością w szególności. Jest nim zakleszczenie.

Z zakleszczeniem mamy do czynienia np. wtedy, gdy jeden wątek

otwiera monitor obiektu X, drugi otwiera monitor obiektu Y, i pierwszy próbuje wywołać synchronizowaną metodę obiektu Y, natomiast drugi próbuje wywołać synchronizowaną metodę obiektu X. Oba wątki będą czekały na zwonienie zasobów blokując je sobie wzajemnie. Taką

sytuację nazywamy zakleszczeniem.

Zakleszczenie jest trudne do wykrycia ponieważ:

 W programie, w którym zakleszczenie jest możliwe zwykle rzadko do niego dochodzi; oba wątki muszą w tym samym czasie być w

odpowiednim miejscu kodu

 W wielu programach występuje więcej niż dwa wątki i dwie

synchronizowane metody; może dojść do zakleszczeń zespołowych Zobacz: Zakleszczenie.java

(16)

Wstrzymaj, wznów, stop

W Java 1.1 wstrzymanie, wznowienie i zatrzymanie wątku odbywało się przez wywołanie metod suspend(), resume() i stop() klasy

Thread. Metody te nie są stosowane w Java 2.

Zobacz: Suspend1_1.java

W Java 2 zrezygnowano z powyższych metod, ponieważ mogły

doprowadzić do poważnych błędów programu, np.: wstrzymanie wątku, gdy ten blokuje zasób krytyczny spowoduje zablokowanie tego zasobu na zawsze, zatrzymanie wątku w trakcie zapisu danych może

spowodować niekompletność zapisywanych danych.

W Java 2 należy tak zaprojektować metodę run() by wątek sam, raz na jakiś czas, sprawdzał czy powinien wstrzymać, wznowić czy

zatrzymać wykonanie. Zwykle osiąga się to za pomocą flag i metod wait() i notify().

(17)

Pięciu filozofów

Stó

ł

F1

F2

F3

F4

F5

Filozof myśli. Gdy zgłodnieje podnosi prawy widelec,

następnie lewy i je. Po

zakończonym posiłku odkłada najpierw prawy a potem lewy widelec i zaczyna myśleć, aż zgłodnieje, itd...

Zadanie:

1. Zaimplementować w Java problem pięciu filozofów za pomocą wątków.

2. Rozwiązać zadanie tak by nie dochodziło do zagłodzenia żadnego filozofa.

Cytaty

Powiązane dokumenty

Informacja o sposobie tworzenia Programu oraz o przebiegu konsultacji. Projekt Programu Współpracy z organizacjami pozarządowymi opracowywany jest przez właściwą

Natomiast ci, którzy zastanawiaj¹ siê, czy intelektualna œcie¿ka, jak¹ pod¹¿aj¹, aby na pewno jest t¹ w³aœciw¹, ksi¹¿ka Prawda i warunki jej mo¿liwoœci mo¿e okazaæ

Staje się więc edukacja kulturalna zadaniem społecznym, usiłującym wpi ­ sać się w dwie znaczące - niekiedy wzajemnie znoszące się i trudne do pogodzenia - tendencje:

Widać, że autor Hermeneutyki fenomenu istnienia porusza się swobodnie na tym rozległym polu badawczym, że stara się nie przeoczyć żadnego ważniejszego artykułu, a tym

Trzech hiperłączy tekstowych będących elementami listy wypunktowanej i dwóch hiperłączy graficznych będących elementami listy numerowanej.. Podstawowymi

Oczywiście ferie zimowe to także czas na aktywność fizyczną, na którą zazwyczaj nie ma tyle czasu w ciągu tygodnia roboczego.. Organizowane są w tym czasie specjalne

Na różnych rynkach za granicznych wytwarza się obecnie dla Polski bardzo korzystna okazja do wzmożenia swego eksportu, a sądzić można o tym fakcie po Kanadzie.. Kraj ten

Ci uczniowie, którzy nie otrzymali rekomendacji, zdają egzaminy wstępne z języka angielskiego i matematyki.. Uczniowie MYP, którzy przychodzą do nas z innych szkół, zdają