W Javie wątki są obiektami zdefiniowanymi za pomocą specjalnego rodzaju klas.



Podobne dokumenty
Współbieżność w środowisku Java

Autor: dr inż. Zofia Kruczkiewicz, Programowanie aplikacji internetowych 1

Współbieżność i równoległość w środowiskach obiektowych. Krzysztof Banaś Obliczenia równoległe 1

Język Java wątki (streszczenie)

Język Java wątki (streszczenie)

Wątki w Javie. Piotr Tokarski

Wielowątkowość. Programowanie w środowisku rozproszonym. Wykład 1.

6.1 Pojęcie wątku programu 6.2 Klasy Timer, TimerTask 6.3 Klasa Thread 6.4 Synchronizacja pracy wątków 6.5 Grupowanie wątków

Wątki. Definiowanie wątków jako klas potomnych Thread. Nadpisanie metody run().

Wątek - definicja. Wykorzystanie kilku rdzeni procesora jednocześnie Zrównoleglenie obliczeń Jednoczesna obsługa ekranu i procesu obliczeniowego

1 Wątki 1. 2 Tworzenie wątków 1. 3 Synchronizacja 3. 4 Dodatki 3. 5 Algorytmy sortowania 4

Kurs programowania. Wykład 8. Wojciech Macyna. 10 maj 2017

Java. Wykład. Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ

Obliczenia równoległe i rozproszone w JAVIE. Michał Kozłowski 30 listopada 2003

Kurs programowania. Wykład 8. Wojciech Macyna

Programowanie wielowątkowe. Tomasz Borzyszkowski

Wielowątkowość mgr Tomasz Xięski, Instytut Informatyki, Uniwersytet Śląski Katowice, 2011

4.1 Napisz kod, w którym definiujesz, tworzysz oraz uruchamiasz wątki z uŝyciem klas java.lang.thread oraz java.lang.runnable.

Programowanie równoległe i rozproszone. Monitory i zmienne warunku. Krzysztof Banaś Programowanie równoległe i rozproszone 1

Języki i Techniki Programowania II. Wykład 7. Współbieżność 1

Współbieżność w Javie

Współbieżność w Javie

Stworzenie klasy nie jest równoznaczne z wykorzystaniem wielowątkowości. Uzyskuje się ją dopiero poprzez inicjalizację wątku.

Programowanie komputerów

Programowanie współbieżne i rozproszone

Aplikacje w Javie- wykład 11 Wątki-podstawy

Podstawy współbieżności

Programowanie równoległe i rozproszone. W1. Wielowątkowość. Krzysztof Banaś Programowanie równoległe i rozproszone 1

Język JAVA podstawy. Wykład 3, część 3. Jacek Rumiński. Politechnika Gdańska, Inżynieria Biomedyczna

Programowanie obiektowe. Literatura: Autor: dr inŝ. Zofia Kruczkiewicz

Dokumentacja do API Javy.

Semafor nie jest mechanizmem strukturalnym. Aplikacje pisane z użyciem semaforów są podatne na błędy. Np. brak operacji sem_post blokuje aplikację.

Multimedia JAVA. Historia

Semafor nie jest mechanizmem strukturalnym. Aplikacje pisane z użyciem semaforów są podatne na błędy. Np. brak operacji sem_post blokuje aplikację.

JAVA W SUPER EXPRESOWEJ PIGUŁCE

Interfejsy w Java. Przetwarzanie równoległe. Wątki.

Obiekt klasy jest definiowany poprzez jej składniki. Składnikami są różne zmienne oraz funkcje. Składniki opisują rzeczywisty stan obiektu.

WSPÓŁBIEŻNOŚĆ. MATERIAŁY:

Marcin Luckner Politechnika Warszawska Wydział Matematyki i Nauk Informacyjnych

Wstęp do programowania 2

Obsługa wyjątków. Rysunek 2-4 Hierarchia dziedziczenia klas wyjątków

Informatyka I. Klasy i obiekty. Podstawy programowania obiektowego. dr inż. Andrzej Czerepicki. Politechnika Warszawska Wydział Transportu 2018

Programowanie obiektowe

Systemy operacyjne. Zajęcia 11. Monitory

Rozdział 4 KLASY, OBIEKTY, METODY

Java: kilka brakujących szczegółów i uniwersalna nadklasa Object

Java. Programowanie Obiektowe Mateusz Cicheński

Programowanie wielowątkowe: podstawowe koncepcje, narzędzia w Javie. J. Starzyński, JiMP2, rok akad. 2005/2006

Aplikacje w środowisku Java

Programowanie obiektowe

Programowanie obiektowe

Programowanie w języku Java - Wyjątki, obsługa wyjątków, generowanie wyjątków

Interfejsy i klasy wewnętrzne

Programowanie obiektowe zastosowanie języka Java SE

Aplikacje Internetowe. Najprostsza aplikacja. Komponenty Javy. Podstawy języka Java

Współbieżność i równoległość w środowiskach obiektowych. Krzysztof Banaś Obliczenia równoległe 1

Aplikacje w środowisku Java

Wykład 7: Pakiety i Interfejsy

JAVA. Java jest wszechstronnym językiem programowania, zorientowanym. apletów oraz samodzielnych aplikacji.

Klasy abstrakcyjne i interfejsy

Programowanie współbieżne Wykład 2. Rafał Skinderowicz

Programowanie obiektowe

Wyjątki. Streszczenie Celem wykładu jest omówienie tematyki wyjątków w Javie. Czas wykładu 45 minut.

Ćwiczenie 1. Przygotowanie środowiska JAVA

Wykład 8: klasy cz. 4

Programowanie współbieżne Wykład 5. Rafał Skinderowicz

Konstruktory. Streszczenie Celem wykładu jest zaprezentowanie konstruktorów w Javie, syntaktyki oraz zalet ich stosowania. Czas wykładu 45 minut.

4. Procesy pojęcia podstawowe

Wykład 4: Klasy i Metody

PHP 5 język obiektowy

Zaawansowane programowanie w C++ (PCP)

Programowanie współbieżne Laboratorium nr 12

Generatory. Michał R. Przybyłek

Wykład 6: Dziedziczenie

Interfejsy. Programowanie obiektowe. Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki Politechniki Wrocławskiej

Wykład 8: Obsługa Wyjątków

Systemy Rozproszone - Ćwiczenie 4

Programowanie w Internecie. Java

Kurs programowania. Wykład 2. Wojciech Macyna. 17 marca 2016

Wykład 5. Synchronizacja (część II) Wojciech Kwedlo, Wykład z Systemów Operacyjnych -1- Wydział Informatyki PB

Laboratorium 03: Podstawowe konstrukcje w języku Java [2h]

Klasy. dr Anna Łazińska, WMiI UŁ Podstawy języka Java 1 / 13

Kurs programowania. Wykład 1. Wojciech Macyna. 3 marca 2016

Wątki (Threads) Potrzeby. Przetwarzanie równoległe i współbieŝne. Cechy programowania wątkowego. Concurrent programming is like

dziedziczenie - po nazwie klasy wystąpią słowa: extends nazwa_superklasy

KLASY, INTERFEJSY, ITP

Mechanizm dziedziczenia

Programowanie obiektowe

PODEJŚCIE OBIEKTOWE. Przykład 1 metody i atrybuty statyczne

4. Procesy pojęcia podstawowe

Kompozycja i dziedziczenie klas

Polimorfizm, metody wirtualne i klasy abstrakcyjne

Techniki programowania INP001002Wl rok akademicki 2018/19 semestr letni. Wykład 3. Karol Tarnowski A-1 p.

Obszar statyczny dane dostępne w dowolnym momencie podczas pracy programu (wprowadzone słowem kluczowym static),

Java - tablice, konstruktory, dziedziczenie i hermetyzacja

PoniŜej znajdują się pytania z egzaminów zawodowych teoretycznych. Jest to materiał poglądowy.

Monitory. Jarosław Kuchta

Język JAVA podstawy. wykład 2, część 1. Jacek Rumiński. Politechnika Gdańska, Inżynieria Biomedyczna

Język Java część 2 (przykładowa aplikacja)

Przetwarzanie równoległe i współbieżne

Transkrypt:

WspółbieŜność W programowaniu sekwencyjnym, kaŝdy program ma początek, sekwencje instrukcji do wykonania i koniec. W kaŝdym momencie działania programu moŝemy wskazać miejsce, w którym znajduje się sterowanie. Taki program stanowi zatem pojedynczy, sekwencyjny przepływ sterowania. Program moŝe jednak składać się z wielu przepływów sterowania, zwanych wątkami (ang. thread). KaŜdy wątek ma początek, sekwencje instrukcji i koniec. Wątek nie jest niezaleŝnym programem, jest wykonywany jako część programu. W programie wiele wątków moŝe być wykonywanych jednocześnie i kaŝdy z nich moŝe wykonywać w tym samym czasie odmienne zadania (ang. tasks). Jeśli program napisany wielowątkowo (ang. multithreaded), wykonywany jest na maszynie wieloprocesorowej, to róŝne wątki mogą być wykonywane w tym samym czasie na róŝnych procesorach. Sterowanie programu w takich przypadkach przebiega współbieŝnie (ang. concurrent). Na komputerach jednoprocesorowych wykonanie programów wielowątkowych jest tylko emulowane. Emulacja ta polega na naprzemiennym przydzielaniu czasu procesora poszczególnym wątkom wg. pewnego algorytmu (zaimplementowanego w systemie operacyjnym). To, w jakim stopniu wątek będzie mógł wykorzystywać procesor, zaleŝy od priorytetu wątku (priorytety zostaną omówione dalej w tym rozdziale). Tak jak w programie sekwencyjnym, kaŝdy wątek ma swoje zarezerwowane zasoby (jak np. licznik instrukcji), lecz oprócz tego moŝe korzystać z zasobów programu, w którym jest wykonywany. W Javie wątki są obiektami zdefiniowanymi za pomocą specjalnego rodzaju klas. Program wielowątkowy definiujemy, na dwa sposoby: jako podklasę klasy Thread; implementując interfejs Runnable. Jeśli zdefiniujemy klasę z moŝliwością pracy jako wątek nie oznacza to automatycznie, Ŝe klasa ta będzie wykonana jako taki. Zostanie to wyjaśnione dalej w tym rozdziale. Przykład prostego programu wielowątkowego Spójrzmy jak wygląda wielowątkowość w praktyce. MoŜemy zdefiniować naszą klasę jako wątek poprzez rozszerzenie klasy java.lang.thread. Ten sposób daje nam bezpośredni dostęp do wszystkich metod kontrolujących wątek zdefiniowanych w klasie Thread. W tej przykładowej aplikacji zdefiniowano dwie klasy: Watek i PierwszyWielowatkowy. Klasa Watek definiuje pojedynczy wątek, który będzie wykonywany w aplikacji PierwszyWielowatkowy, klasa ta dziedziczy z klasy Thread będącej częścią pakietu java.lang.

Przykład 2.24 Rozszerzenie klasy Thread class Watek extends Thread String wysun = ""; public Watek(String str, int numer) super(str); // Ustawienie wcięcia z jakim będzie wyświetlana nazwa Watku for (int i = 1; i < numer; i++) wysun = wysun + "\t"; public void run() for (int i = 0; i < 4; i++) System.out.println(wysun + i + " " + getname()); sleep( (int)(math.random() * 1000) ); catch ( InterruptedException e ) e.printstacktrace(); System.out.println(wysun + getname()+ " koniec" ); Pierwszą metodą klasy Watek jest konstruktor z dwoma argumentami: nazwa wątku, typu String i parametr określający wcięcie, z jakim będzie wyświetlana na ekranie nazwa wątku. W konstruktorze tym pierwszą instrukcją jest wywołanie konstruktora nadklasy, który ustawia nazwę wątku. Następnie w bloku for ustawiane jest wcięcie z jakim na ekranie Następną metodą klasy Watek jest metoda run(). Metoda run jest najwaŝniejszą metodą kaŝdego wątku, w której zdefiniowane są wszystkie działania wykonywane przez wątek. Metoda run() klasy Watek zawiera pętlę for wykonywaną cztery razy. W kaŝdej iteracji metoda ta wyświetla na ekranie numer iteracji i nazwę wątku z odpowiednim wcięciem zaleŝnym od parametru numer konstruktora. Po wyświetleniu tego napisu wątek za pomocą metody sleep() jest usypiany na przedział czasu wylosowany przez metodę random klasy Math. Po wykonaniu wszystkich iteracji wyświetlana jest na ekranie nazwa wątku z napisem "koniec". Klasa PierwszyWielowatkowy definiuje aplikację. W metodzie main() tworzone są cztery wątki o nazwach: Janek, Magda, Wacek i Ola (przypuśćmy, Ŝe kaŝda z tych osób ma zadzwonić do czworga znajomych, kto pierwszy zdoła to zrobić, ten wygrywa). Druga metoda tej klasy, znana nam i zdefiniowana juŝ w tej pracy, pauza() zatrzymuje wyniki pracy aplikacji na ekranie. class PierwszyWielowatkowy public static void main (String[] args) throws Exception new Watek("Janek",1).start(); new Watek("Magda",2).start(); new Watek("Wacek",3).start(); new Watek("Ola",4).start(); pauza(); static void pauza() throws Exception /*... Zdefiniowana juŝ wcześniej w tej pracy */

W metodzie main() wszystkie wątki zaraz po ich utworzeniu są uruchamiane dzięki uŝyciu metody start(). Po skompilowaniu i uruchomieniu tej aplikacji efekt działania będzie podobny do przedstawionego na ekranie. Ilustracja 2-9 Przykładowy efekt wykonania aplikacji PierwszyWielowatkowy. Widać, Ŝe wyniki działania wszystkich wątków są na ekranie przemieszane. Dzieje się tak dlatego, Ŝe wszystkie wątki typu Watek działają jednocześnie. Wszystkie metody run() wykonywane są jednocześnie i wyprowadzają na ekran efekty swojego działania w tym samym czasie. Prócz czterech wątków utworzonych w metodzie main() wciąŝ działa wątek główny aplikacji. Widzimy, Ŝe pierwszym napisem jaki wyprowadzono na ekran jest: Nacisnij Enter... który wyprowadza na ekran metoda pauza(). Jest tak mimo tego, Ŝe w kodzie metody main() metoda pauza() jest na samym końcu. To pokazuje nam, Ŝe w chwili utworzenia nowych wątków działają one niezaleŝnie od wątku głównego programu. Ciało wątku Wszystkie zadania, jakie ma wykonywać wątek umieszczone są w metodzie run wątku. Po utworzeniu i inicjalizacji wątku, środowisko przetwarzania wywołuje metodę run. W ciele metody run często pojawia się pętla. Na przykład, wątek odpowiedzialny za animację w pętli w metodzie run moŝe wyświetlać serię obrazków. Niekiedy metoda run wątku

wykonuje operacje, które zajmują duŝo czasu np. ładowanie i odgrywanie dźwięków lub filmów. Implementacja interfejsu Runnable W wielu aplikacjach mamy do czynienia z koniecznością implementacji wielodziedziczenia, np. chcemy zdefiniować klasę, która ma własności wątku i jednocześnie rozszerza właściwości jakiejś innej klasy. PoniewaŜ w Javie nie jest moŝliwe wielodziedziczenie, rozwiązaniem w takim przypadku jest implementacja interfejsu Runnable. Definicja interfejsu Runnable przedstwia się następująco: public interface java.lang.runnable // Metody public abstract void run(); W rzeczywistości klasa Thread takŝe implementuje interfejs Runnable. Interfejs Runnable ma tylko jedną metodę: run. Kiedy definiujemy klasę jako implementującą ten interfejs, musimy zadeklarować metodę run. W metodzie run wykonywane są wszystkie zadania, które mają być realizowane przez dany wątek. Przykład klasy implementującej interfejs Runnable: class MojaKlasa extends java.applet.applet implements Runnable public void run() //ciało metody run //... inne metody klasy MojaKlasa Spójrzmy jak wygląda aplikacja, która wykonuje te same zadania, co przedstawiony wcześniej program, i jednocześnie wykonywana jest wielowątkowo, implementując interfejs Runnable. Przykład 2.25 Implementacja interfejsu Runnable Główne zmiany w porównaniu z wcześniejsza wersją zaznaczone są pogrubioną czcionką. class WatekPodstawowy implements Runnable String wysun = ""; // W polu danych biezacy przechowywana będzie referencja // do wątku, w którym wykonana zostanie klasa WatekPodstawowy Thread biezacy; public WatekPodstawowy( int numer) // metoda statyczna currentthread() klasy Thread zwaca // referencję do bieŝącego wątku biezacy = Thread.currentThread(); for (int i = 1; i < numer; i++) wysun = wysun + "\t";

public void run() for (int i = 0; i < 4; i++) // dzięki referencji biezacy moŝemy na rzecz tego // wątku wykonać metodę getname() (z klasy Thread) System.out.println(wysun + i + " " + biezacy.getname()); biezacy.sleep((int)(math.random() * 1000)); catch (InterruptedException e) System.out.println(wysun + biezacy.getname()+ " koniec" ); Klasa DrugiWielowatkowy róŝni się od klasy PierwszyWielowatkowy o wiele bardziej niŝ klasa WatekPodstawowy od jej poprzedniczki. W tamtej klasie tworzyliśmy nowe wątki i uruchamialiśmy je. W tym przypadku mamy tablice wątków watki[], której elementy są wątkami kontrolującymi wykonanie obiektów klasy WatekPodstawowy. Przykład 2.26 Definicja klasy DrugiWielowatkowy public class DrugiWielowatkowy // deklaracja tablicy wątków, deklarujemy ją jako static, // bowiem tylko do pól statycznych klasy moŝemy się // odwołać w statycznej metodzie main() static Thread watki[]; public static void main (String[] args) throws Exception // przypisanie do pola danych watki tablicy referencji // do obiektów typu Thread watki = new Thread[4]; // inicjalizacja elementów tablicy watki, // utworzenie nowych wątków watki[0] = new Thread( new WatekPodstawowy(1),"Janek"); watki[1] = new Thread( new WatekPodstawowy(2),"Magda"); watki[2] = new Thread( new WatekPodstawowy(3),"Wacek"); watki[3] = new Thread( new WatekPodstawowy(4),"Ola"); // uruchomienie wątków for (int i=0; i<4; i++) watki[i].start(); pauza(); static void pauza() throws Exception /*... Zdefiniowana juŝ wcześniej w tej pracy */ Stany wątku W czasie swego istnienia wątek moŝe znajdować się w jednym z kilku stanów. PoniŜszy rysunek przedstawia stany, w jakich moŝe znajdować się wątek podczas swego Ŝycia oraz metody, których wywołanie powoduje przejście wątku do następnego stanu. Diagram ten nie jest kompletnym, skończonym diagramem stanów wątku ale obejmuje najbardziej interesujące i najczęściej występujące stany, w jakich wątek moŝe się znaleźć.

Rysunek 2-5 Stany wątków. Omówmy teraz poszczególne stany: nowy wątek PoniŜsza instrukcja tworzy nowy wątek ale nie uruchamia go, lecz pozostawia wątek w stanie "nowy wątek". Thread mojwatek = new MojaKlasaWatku(); Po wykonaniu tej instrukcji mamy zaledwie pusty obiekt Thread. adne zasoby systemowe nie zostały jeszcze alokowane dla tego wątku. Kiedy wątek znajduje się w tym stanie, moŝemy jedynie wykonać metod. start, uruchamiającą wątek, lub stop, kończącą działania wątku. Wszelkie próby wywołania innych metod dla wątków w tym stanie nie mają sensu i powodują wystąpienie wyjątku IllegalThreadStateException. wykonywany Przeanalizujmy poniŝsze dwie linie kodu: Thread mojwatek = new MojaKlasaWatku(); mojwatek.start(); Metoda start tworzy zasoby systemowe potrzebne do wykonania wątku, przygotowuje wątek do uruchomienia, oraz woła metodę run. Od tego momentu wątek jest w stanie "wykonywany". Nie oznacza to jednak automatycznie, Ŝe wątek zostaje uruchomiony. Wiele komputerów ma tylko jeden procesor, co powoduje, Ŝe niemoŝliwe jest uruchomienie wielu

wątków w tym samym momencie.?rodowisko przetwarzania Javy musi implementować system przydziału czasu procesora (ang. scheduler), który dzieli czas procesora między wszystkie wątki będące w stanie "wykonywany". Więcej informacji o przydzielaniu czasu procesora wątkom znajduje się w rozdziale poświęconym priorytetom wątków. nie wykonywany Wątek przechodzi do stanu "nie wykonywany" gdy zachodzi jedno z poniŝszych zdarzeń: wywołano jego metodę sleep, wywołano jego metodę suspend, wątek wykonuje swoją metodę wait, wątek jest zablokowany przy operacji wejścia / wyjścia (ang. I/O). Przykładowo, wykonanie metody sleep(10000) w poniŝszym kodzie powoduje uśpienie bieŝącego wątku na 10 sekund (10 000 milisekund): Thread.sleep(10000); catch (InterruptedException e) W czasie tych 10 sekund gdy wątek jest uśpiony, nawet gdy procesor staje się dostępny dla tego wątku, wątek ten nie zostaje uruchomiony. Po upływie 10 sekund wątek przechodzi do stanu "wykonywany" i jeśli procesor jest dostępny, jest on uruchamiany. Dla kaŝdego przypadku przejścia wątku do stanu "nie wykonywany" istnieją specyficzne warunki, jakie muszą być spełnione, aby nastąpił powrót do stanu "wykonywany". PoniŜej przedstawiono warunki, jakie muszą być spełnione, aby nastąpił powrót do stanu "wykonywany": jeśli wątek uśpiono (metoda sleep ), musi upłynąć określona liczba milisekund, jeśli wątek zawieszono (metoda suspend), inny wątek musi wywołać metodę resume wątku, powodującą jego odwieszenie, jeśli wątek czeka na np. ustawienie jakiejś zmiennej, to obiekt, do którego naleŝy ta zmienna, musi ją odstąpić a następnie wywołać metodę notify lub notifyall, jeśli wątek jest zablokowany przy operacjach wejścia / wyjścia, wtedy operacje te muszą być zakończone. zakończony Wątek moŝe zakończyć działanie z dwu powodów: albo naturalnie zakończy swe działanie albo zostanie zabity (ang. kill). Wątek naturalnie kończy swoje działanie wtedy, gdy jego metoda run kończy się normalnie. W poniŝszym przykładzie pętla while wykonywana jest 50 razy i metoda run naturalnie kończy swoje działanie: public void run() int i = 1;

while (i < 51) System.out.println( i + " iteracja"); i++; MoŜemy takŝe zabić wątek w kaŝdym momencie poprzez wywołanie jego metody stop. W poniŝszym przykładzie tworzony i uruchamiany jest wątek mojwatek, następnie bieŝący wątek jest usypiany na 10 sekund. Kiedy bieŝący wątek się budzi, w przedostatniej linii przykładu wątek mojwatek zostaje zabity: Thread mojwatek = new MojaKlasaWatku(); mojwatek.start(); Thread.sleep(10000); catch (InterruptedException e) mojwatek.stop(); Metoda stop generuje w wątku obiekt (wyjątek) ThreadDeath, słuŝący do zabicia go. Oznacza to, Ŝe wątek w takim przypadku zabijany jest asynchronicznie. Wątek zostaje zabity wtedy, gdy rzeczywiście odbierze wyjątek ThreadDeath. Metoda stop oznacza nagłe zakończenie wykonania metody run wątku. Jeśli metoda run wykonuje jakieś waŝne obliczenia, metoda stop moŝe spowodować przerwanie wykonywania programu w stanie niespójnym. Dlatego nie powinno się wołać metody stop wtedy, gdy chcemy zakończyć wątek, lecz zrobić to w łagodniejszy sposób np. poprzez ustawienie flagi, która informuje metodę run, Ŝe powinna zakończyć swoje wykonanie. Wyjątek IllegalThreadStateException?rodowisko przetwarzania generuje wyjątek IllegalThreadStateException, gdy próbujemy wywołać metodę wątku a wątek znajduje się w takim stanie, który nie pozwala na wywołanie tej metody. Przykładowo w stanie "nie wykonywany" wyjątek ten występuje, gdy próbujemy wywołać metodę suspend. Metoda isalive Interfejs programistyczny dla klasy Thread zawiera metodę isalive. Wynikiem wykonania metody isalive jest wartość true, gdy wątek został uruchomiony a nie został jeszcze zakończony. Gdy wynikiem wykonania metody isalive jest wartość false oznacza to, Ŝe wątek jest albo w stanie "nowy wątek" lub "zakończony". Gdy wynikiem jest wartość true wiemy, Ŝe jest albo w stanie "wykonywany" lub "nie wykonywany". Priorytet wątku Priorytet wątku informuje program szeregujący wątki Javy (ang. Java thread scheduler), kiedy nasz wątek powinien być wykonywany w odniesieniu do innych wątków. Wcześniej wspomniano, Ŝe wątki wykonywane są równolegle. Jeśli konceptualnie jest to prawda, w praktyce zazwyczaj tak nie jest. Większość komputerów posiada tylko jeden

procesor, więc w danej chwili czasu wykonywany moŝe być tylko jeden wątek i wielowątkowość jest emulowana. Wykonanie wielu wątków na pojedynczym procesorze w jakiejś kolejności nazywane jest szeregowaniem (ang. scheduling).?rodowisko przetwarzania Javy implementuje bardzo prosty, deterministyczny algorytm szeregowania znany jako "planowanie priorytetowe" (ang. fixed priority scheduling). KaŜdemu wątkowi przypisuje się pewien priorytet, po czym przydziela się procesor temu wątkowi, którego priorytet jest najwyŝszy. Gdy nowy wątek jest tworzony, dziedziczy priorytet z wątku, który go utworzył. Priorytety wątku mogą być modyfikowane w kaŝdej chwili po utworzeniu wątku poprzez uŝycie metody setpriority. Priorytet wątku jest liczbą typu integer o wartości większej lub równej MIN_PRIORITY i niewiększej niŝ MAX_PRIORITY (są to stałe zdefiniowane w klasie Thread o wartości odpowiednio 1 i 10). Im większa wartość liczby określającej priorytet, tym priorytet wątku jest większy. W momencie, gdy wiele wątków jest gotowych do wykonania, środowisko przetwarzania wybiera do wykonania wątek z najwyŝszym priorytetem. Tylko w przypadku, gdy wątek zatrzymuje się, ustępuje czas procesora (metoda yield) lub przechodzi do stanu "nie wykonywany", wątek o niŝszym priorytecie zaczyna być wykonywany. Gdy dwa wątki o tym samym priorytecie czekają na przydzielenie im czasu procesora, program szeregujący wybiera jeden z nich i przydziela czas według algorytmu "planowania rotacyjnego" (ang. round-robin). W algorytmie tym ustala się małą jednostkę czasu, nazywaną kwantem czasu lub odcinkiem czasu. Kolejka procesów gotowych do wykonania jest traktowana jako kolejka cykliczna. Planista przydziału procesora przegląda tę kolejkę i kaŝdemu wątkowi przydziela odcinek czasu nie dłuŝszy od jednego kwantu czasu. Gdy wątek ma czas wykonania dłuŝszy, niŝ kwant czasu, to nastąpi przerwanie wykonywania wątku i zostanie on odłoŝony na koniec kolejki. Wybrany wątek będzie wykonywany do momentu, gdy jeden z poniŝszych warunków będzie spełniony: wątek o wyŝszym priorytecie znalazł się w stanie "wykonywany", wątek ustępuje procesor lub metoda run kończy działanie, w systemie, w którym stosuje się przydzielanie kwantów czasu, kwant czasu się skończył. Jeśli w jakimś momencie wątek z większym priorytetem, niŝ inne wątki w stanie "wykonywany" znajdzie się tym w stanie, to środowisko przetwarzania Javy wybiera do wykonania wątek z nowym najwyŝszym priorytetem. Uwaga: W danym momencie wątek o najwyŝszym priorytecie jest wykonywany. JednakowoŜ nie jest to gwarantowane. Program szeregujący wątki moŝe wybrać do wykonania wątek z niŝszym priorytetem, aby uniknąć zagłodzenia. Z tych powodów, poleganie na priorytetach nie gwarantuje nam poprawności algorytmu. Demony KaŜdy wątek Javy moŝe zostać demonem (ang. daemon thread). Wątek będący demonem zajmuje się obsługiwaniem innych wątków uruchomionych w tym samym procesie, co wątek demona. Metoda run wątku demona jest przewaŝnie nieskończoną pętlą, w której demon czeka na zgłoszenia zapotrzebowania na usługi dostarczane przez ten wątek.

Przykładowo, przeglądarka HotJava uŝywa do czterech demonów nazwanych "Image Fetcher", które dostarczają obrazków z dysku lub sieci dla wątków, które tego potrzebują. Wykonanie programu kończy się z chwilą zakończenia ostatniego wątku, który nie jest demonem. Dzieje się tak dlatego, Ŝe gdy w programie nie istnieją juŝ inne wątki prócz demonów, demony nie mają juŝ dla kogo dostarczać usług i ich dalsze istnienie nie ma sensu. Aby wątek został demonem uŝywany metody setdaemon z argumentem równym true. W celu sprawdzenia, czy wątek jest demonem uŝywana jest metoda isdaemon. Grupowanie wątków KaŜdy wątek Javy jest członkiem grupy wątków (ang. thread group). Grupowanie wątków w jednym obiekcie pozwala na jednoczesne manipulowanie wszystkimi zgrupowanymi wątkami. Przykładowo, moŝemy uruchomić (metoda start ) lub zawiesić (suspend) wszystkie zgrupowane wątki dzięki wykonaniu jednej metody. Grupowanie wątków w Javie zaimplementowano w klasie java.lang.threadgroup.?rodowisko przetwarzania Javy umieszcza nowy wątek w grupie wątków podczas jego tworzenia. Kiedy tworzymy nowy wątek, moŝemy środowisku przetwarzania pozwolić na umieszczenie wątku w domyślnej grupie wątków lub moŝemy explicite zadeklarować nową grupę wątków i dodać do niej nasz wątek. Po tym, jak wątek stał się członkiem jakiejś grupy wątków podczas tworzenia, nie moŝna przenosić wątku do innej grupy. Jeśli tworzymy nowy wątek bez specyfikacji jego grupy w konstruktorze, środowisko przetwarzania automatycznie umieszcza nowy wątek w tej samej grupie co wątek, który go utworzył. Gdy aplikacja Javy zostaje uruchomiona, środowisko przetwarzania tworzy automatycznie obiekt ThreadGroup o nazwie 'main' (nasz wątek główny naleŝy do grupy wątków 'main'). I gdy nie zadeklarujemy tego inaczej, wszystkie utworzone wątki staną się członkami grupy wątków 'main'. Uwaga: Gdy tworzymy wątek w aplecie, nowe wątki mogą być członkami innych grup wątków niŝ 'main', zaleŝnie od przeglądarki, w której aplet jest uruchamiany. Wątki w aplecie omówione zostaną w rozdziale: Wielowątkowość w apletach. Jeśli chcemy utworzony wątek umieścić w grupie wątków innej niŝ domyślna, musimy tę grupę wyspecyfikować w momencie, gdy nowy wątek jest tworzony. Klasa Thread ma trzy konstruktory pozwalające ustawić nową grupę wątków: public Thread(ThreadGroup grupawatkow, Runnable target) public Thread(ThreadGroup grupawatkow, String name) public Thread(ThreadGroup grupawatkow, Runnable target, String name) KaŜdy z tych konstruktorów tworzy nowy wątek, który jest członkiem wyspecyfikowanej grupy wątków. Przykładowo, poniŝszy kawałek kodu tworzy nową grupę wątków (mojagrupaw) a następnie tworzy nowy wątek mojwatek naleŝący do tej grupy: ThreadGroup mojagrupaw = new ThreadGroup("Moja grupa watkow");

Thread mojwatek = new Thread(mojaGrupaW, "watek w mojej grupie"); Grupa wątków, do której dołączamy nasz wątek nie musi być grupą zadeklarowaną przez nas, moŝe to być grupa stworzona przez środowisko wykonawcze Javy lub grupa wątków stworzona przez program (np. przeglądarkę), w którym nasz aplet uruchomiono. Jeśli chcemy sprawdzić, do jakiej grupy naleŝy nasz wątek, moŝemy w tym celu uŝyć metody getthreadgroup(): grupa = mojwatek.getthreadgroup(); Wynikiem wykonania tej metody jest referencja do obiektu reprezentującego grupę wątków. Gdy mamy dostęp do obiektu reprezentującego grupę wątków, moŝemy uzyskać róŝnego rodzaju informacje np. jakie jeszcze inne wątki naleŝą do tej grupy, moŝemy takŝe modyfikować wątki naleŝące do tej grupy np. moŝemy je uśpić, zatrzymać lub zakończyć w pojedynczym wywołaniu metody np. uŝycie metody: grupa.suspend(); w tym przypadku powoduje, Ŝe wszystkie wątki naleŝące do grupy wątków grupa zostają uśpione. Grupa wątków moŝe zawierać dowolną liczbę wątków. Wątki naleŝące do jednej grupy przewaŝnie są jakoś powiązane ze sobą np. wspólnym wątkiem, który je utworzył, zadaniami jakie wykonują w programie, lub momentem, w którym powinny zostać uruchomione i zatrzymane. Obiekt klasy ThreadGroup moŝe zawierać nie tylko wątki ale takŝe inne obiekty klasy ThreadGroups. NajwyŜej w hierarchii wątków aplikacji Javy znajduje się grupa wątków o nazwie 'main'. W grupie wątków 'main' moŝemy tworzyć nowe wątki lub grupy wątków. W grupach wątków moŝna z kolei tworzyć następne wątki lub grupy wątków. W rezultacie hierarchia wątków moŝe przybrać wygląd jak na poniŝszym rysunku:

Rysunek 2-6 Przykładowa hierarchia grup wątków i wątków w aplikacji Javy. Klasa ThreadGroup zawiera metody, które moŝemy następująco posegregować: metody zarządzające kolekcjami - są to metody zarządzające grupą wątków i podgrupami zawartymi w grupie wątków, metody operujące na grupie - metody te ustawiają i pobierają abuty obiektu ThreadGroup, metody operujące na wszystkich wątkach w grupie - ten zbiór metod wykonuje niektóre operacje, jak uruchamianie lub zatrzymywanie, wszystkich wątków w grupie wątków ThreadGroup. Synchronizacja wątków RozwaŜmy teraz przypadek, gdy wykonywane są dwa niezaleŝne wątki, które współdzielą dane i stan kaŝdego z nich zaleŝy od stanu drugiego wątku. Jedną z takich sytuacji jest problem typu Producent/Konsument (ang. producer/consumer), gdzie producent generuje strumień danych, które są wykorzystywane (konsumowane) przez konsumenta. Strumień ten stanowi wspólny zasób, wątki muszą być zatem synchronizowane. ZałóŜmy, Ŝe producent generuje liczby od 0 do 9, które są następnie składowane w obiekcie typu Pudelko. Producent, po włoŝeniu do pudełka liczby i wydrukowaniu jej na ekranie, zostaje uśpiony na losowo wybrany czas między 0 a 100 milisekund, zanim przejdzie do następnego cyklu produkcji liczby. Przykład 2.27 Aplikacja Producent/Konsument class Producent extends Thread private Pudelko pudelko; private int m_nliczba; public Producent(Pudelko c, int liczba)

pudelko = c; this.m_nliczba = liczba; public void run() for (int i = 0; i < 10; i++) pudelko.wloz(i); System.out.println("Producent #" + this.m_nliczba + " wlozyl: " + i); sleep((int)(math.random() * 100)); catch (InterruptedException e) Konsument podczas swego działania konsumuje wszystkie liczby złoŝone w pudełku, wyprodukowane przez Producenta, tak szybko, jak staną się one dostępne. class Konsument extends Thread private Pudelko pudelko; private int m_nliczba; public Konsument(Pudelko c, int Liczba) pudelko = c; this.m_nliczba = Liczba; public void run() int wartosc = 0; for (int i = 0; i < 10; i++) wartosc = pudelko.wez(); System.out.println("Konsument #" + this.m_nliczba + " wyjal: " + wartosc); Producent i konsument w tym przykładzie współdzielą dane przez wspólny obiekt typu Pudelko. Konsument ma prawo pobrać kaŝdą wyprodukowaną liczbę tylko raz. Synchronizacja między tymi dwoma wątkami występuje w metodach wez() i wloz() obiektu Pudelko. Klasa Pudelko wygląda następująco: class Pudelko private int m_nzawartosc; // to jest znienna warunkowa // do której dostęp synchronizujemy, (omówione później) private boolean m_bdostepne = false; public synchronized int wez()

while (m_bdostepne == false) wait(); catch (InterruptedException e) m_bdostepne = false; notifyall(); return m_nzawartosc; public synchronized void wloz(int wartosc) while (m_bdostepne == true) wait(); catch (InterruptedException e) m_nzawartosc = wartosc; m_bdostepne = true; notifyall(); Klasa Pudelko zostanie dokładnie omówiona później w tym rozdziale. Po wykonaniu aplikacji ProdKonsTest, która tworzy obiekty typu Pudelko oraz Producent i Konsument, a następnie uruchamia wątki Producenta i Konsumenta (współdzielące obiekt typu Pudelko) : class ProdKonsTest public static void main(string[] args) throws Exception Pudelko c = new Pudelko(); Producent p1 = new Producent(c, 1); Konsument c1 = new Konsument(c, 1); p1.start(); c1.start(); pauza(); static void pauza() throws java.io.ioexception System.out.println("Nacisnij Enter..."); System.in.read(); Na ekranie otrzymamy:

Ilustracja 2-10 Wynik działania aplikacji KonsProdTest. W naszym programie uŝyto dwóch mechanizmów synchronizacji wątków Producenta i Konsumenta: monitora i dwóch metod: wait oraz notifyall. Monitory Obiekty takie, jak Pudelko, które są współdzielone pomiędzy dwa wątki, i do których dostęp musi być synchronizowany nazywane są zmiennymi warunkowymi (ang. condition variable). Java pozwala synchronizować wątki pracujące ze zmiennymi warunkowymi dzięki uŝyciu monitorów. Monitory związane są ze specyficznymi danymi (nazywanymi zmiennymi warunkowymi) i działają jako blokada zakładana na tych danych. Gdy wątek zajmie monitor dla jakiejś danej, inne wątki do czasu zwolnienia monitora zostają zablokowane i nie mogą odczytywać lub modyfikować danych. Segment kodu w programie, w którym następuje dostęp do tej samej danej z róŝnych wątków nazywany jest sekcją krytyczną (ang. critical section). W Javie sekcję krytyczną oznaczmy przy uŝyciu słowa kluczowego synchronized. Generalnie, w programach Javy, sekcją krytyczną są metody. MoŜna oznaczyć mniejszy kawałek kodu jako sekcję krytyczną. Jednak taki sposób programowania nie jest zgodny z paradygmatem programowania obiektowego i prowadzi do zaciemnienia kodu, który staje się przez to trudny do zrozumienia i sprawdzenia poprawności (ang. debug). Najlepszym rozwiązaniem jest uŝywanie synchronizacji tylko na poziomie metod. W Javie kaŝdy obiekt, który ma metody synchroniczne posiada swój monitor. Przedstawiona wcześniej klasa Pudelko ma dwie metody synchroniczne: metodę wloz(), uŝywaną do zmiany wartości w obiekcie typu Pudelko i metodę wez(), która jest uŝywana do pobrania liczby przechowywanej w obiekcie Pudelko. Oznacza to, Ŝe system skojarzy z kaŝdym

obiektem typu Pudelko unikalny monitor. W przedstawionym poniŝej kodzie klasy Pudelko elementy wyróŝnione pogrubioną czcionką słuŝą do synchronizacji wątków: class Pudelko private int m_nzawartosc; private boolean m_bdostepne = false; public synchronized int wez() // monitor zostaje zajęty przez Konsumenta while (m_bdostepne == false) wait(); // metoda wait() tymczasowo zwalnia monitor, // (dokładny opis w rozdziale poświęconym metodzie wait ) catch (InterruptedException e) m_bdostepne = false; notifyall(); return m_nzawartosc; // monitor zostaje zwolniony przez Konsumenta public synchronized void wloz(int wartosc) // monitor zostaje zajęty przez Producenta while (m_bdostepne == true) wait(); // metoda wait() tymczasowo zwalnia monitor catch (InterruptedException e) m_nzawartosc = wartosc; m_bdostepne = true; notifyall(); // monitor zostaje zwolniony przez Producenta Klasa Pudelko posiada dwa pola danych: m_nzawartosc, które stanowi bieŝącą zawartość pudełka oraz pole danych m_bdostepne typu boolean, które określa, czy zawartość pudełka moŝe być pobrana. Gdy zmienna m_bdostepne jest równa true oznacza to, Ŝe Producent właśnie umieścił nową wartość w Pudełku, a Konsument jeszcze jej nie pobrał. Konsument moŝe pobrać daną z Pudełka tylko wtedy, gdy zmienna m_bdostępne jest równa true. Jeśli usuniemy z metod wez() i wloz() klasy Pudelko elementy kodu odpowiedzialne za synchronizacje (oznaczone pogrubioną czcionką). Wynik działania aplikacji ProdKonsTest będzie następujący (lub podobny):

Ilustracja 2-11 Wynik działania aplikacji ProdKonsTest bez mechanizmów synchronizacji. Jak widać, Konsument pobiera liczby z Pudełka nie czekając na wyprodukowanie przez Producenta kolejnej liczby. Wróćmy jednak do naszego przykładu, gdzie synchronizacja jest zapewniona. PoniewaŜ klasa Pudelko ma dwie metody synchroniczne, dla kaŝdego obiektu klasy Pudelko tworzony jest oddzielny monitor. W momencie gdy sterowanie znajdzie się w metodzie synchronicznej, wątek, który wywołał tę metodę zajmuje monitor obiektu, którego metodę wywołano. Inne wątki nie mogą wołać metod synchronicznych tego obiektu do czasu zwolnienia monitora. Monitory w Javie są wielodostępne (ang. reentrant). Oznacza to, Ŝe wątek, który zajął monitor jakiegoś obiektu moŝe wołać inne metody synchroniczne obiektu. Zawsze wtedy, gdy Producent woła metodę wloz, Producent zajmuje monitor obiektu Pudelko, co powoduje, Ŝe Konsument nie moŝe wywołać metody wez do czasu zwolnienia monitora. Gdy metoda wloz kończy działanie, Producent zwalnia monitor i odblokowuje obiekt typu Pudelko. Podobnie gdy Konsument woła metodę wez klasy Pudelko, Konsument zajmuje monitor obiektu typu Pudelko, co powoduje, Ŝe Producent nie moŝe wywołać metody wloz do czasu zwolnienia monitora. Operacje zajmowania i zwalniania monitora są wykonywane automatycznie przez środowisko wykonawcze Javy, co zapewnia integralność danych i chroni przed wystąpieniem sytuacji wyjątkowych spowodowanych operacjami na monitorach. Metody notifyall i wait Metody wait i notifyall naleŝą do klasy java.lang.object i mogą być wywoływane tylko przez wątki, które załoŝyły blokadę.

Metoda notifyall informuje wszystkie wątki oczekujące na monitor zajęty przez bieŝący wątek o zwolnieniu tego monitora i budzi te wątki. PrzewaŜnie jeden z oczekujących wątków zajmuje monitor i wykonuje swoje zadanie. W naszym przykładzie, metody wait i notifyall słuŝą do koordynacji wkładania i wyjmowania liczb z Pudełka. Wątek Konsumenta woła metodę wez i zajmuje monitor obiektu Pudelko na czas wykonania metody wez. Na końcu metody wez, wywołanie metody notifyall budzi wątek Producenta oczekujący na monitor obiektu Pudelko. W tym momencie wątek Producenta zajmuje monitor i wykonuje swoje zadanie. public synchronized int wez() while (m_bdostepne == false) wait(); catch (InterruptedException e) m_bdostepne = false; notifyall(); // zawiadomienie Producenta return m_nzawartosc; Gdy wiele wątków oczekuje na monitor, system wykonawczy Javy wybiera jeden z oczekujących wątków do wykonania, nie jest jednak określone, który z oczekujących wątków zostanie wybrany. W metodzie wloz, notifyall działa podobnie jak w metodzie wez, budzi wątek Konsumenta, oczekujący na zwolnienie monitora przez Producenta. W klasie Object jest zadeklarowana takŝe metoda notify, która budzi jeden wybrany wątek oczekujący na monitor. W tej sytuacji, kaŝdy z pozostałych wątków oczekuje dalej do czasu odstąpienia monitora i wybrania go przez system wykonawczy. UŜycie notify moŝe być Źle uwarunkowane, to jest, moŝe zawieść, gdy warunki zmienią się trochę. W programowaniu współbieŝnym ustalenie wszystkich zdarzeń, jakie mogą zajść podczas wykonania programu jest bardzo trudne. Rozwiązanie wykorzystujące metodę notifyall jest stabilniejsze niŝ wykorzystujące metodę notify. W przypadku, gdy nie zamierzamy dokładnie analizować programu wielowątkowego lepiej będzie, gdy zastosujemy metodę notifyall. Wątek wołający metodę wait musi posiadać monitor. Metoda wait powoduje zwolnienie posiadanego monitora i przejście w stan oczekiwania do czasu, aŝ inny wątek powiadomi (ang. notify) go o zwolnieniu monitora obiektu. Wtedy wątek ten przejmuje monitor i kontynuuje swoje działanie. Metody wait uŝywamy wraz z metodami notify lub notifyall do koordynacji działania wielu wątków uŝywających tych samych zasobów. W metodzie wez znajduje się pętla while. Gdy zmienna m_bdostepne jest równa false, Producent nie wyprodukował jeszcze nowej liczby i Konsument musi czekać, więc wywoływana jest metoda wait. Powoduje to zwolnienie monitora obiektu Pudelko i Producent zajmuje ten monitor i moŝe wyprodukować liczbę. Gdy Producent w metodzie wloz woła metodę notifyall, Konsument budzi się i kontynuuje pętlę while. Jeśli liczba jest juŝ wyprodukowana (m_bdostępne == true), pętla while zostaje opuszczona i kontynuowane jest wykonanie metody wez.

public synchronized int wez() while (m_bdostepne == false) // czeka na wywołanie przez Producenta notifyall() wait(); catch (InterruptedException e) m_bdostepne = false; notifyall(); return m_nzawartosc; Zasada działania metody wloz jest podobna, implementuje oczekiwanie na wątek Konsumenta aŝ skonsumuje on wyprodukowaną liczbę, gdy to nastąpi oczekujący wątek Producenta moŝe włoŝyć następną liczbę do Pudełka. Poza uŝytą przez nas wersją metody wait w klasie Object mamy zadeklarowane jeszcze dwie inne wersje metody wait: wait(long timeout) - gdzie parametr timeout oznacza maksymalny czas oczekiwania na zwolnienie monitora (metody notify lub notifyall) w milisekundach; wait(long timeout, int nanos) - gdzie parametr timeout podobnie, jak wyŝej oznacza maksymalny czas oczekiwania na zwolnienie monitora w milisekundach a nanos dodatkowy czas w nanosekundach o zakresie od 0 do 999999. Metody wait i notify powinny być wołane przez wątki, które zajmują monitor danego obiektu. Wątek moŝe stać się właścicielem monitora obiektu na trzy sposoby: poprzez wykonanie synchronicznej metody tego obiektu, poprzez wykonanie ciała synchronizowanego wyraŝenia, które synchronizuje obiekt, dla obiektów typu Class, dzięki wykonaniu synchronizowanej metody statycznej tej klasy. Gdy piszemy program, w którym kilka wątków pracuje współbieŝnie musimy pamiętać, Ŝe mogą wystąpić sytuacje, w których nastąpi zagłodzenie (ang. starvation) lub zakleszczenie wątków (ang. deadlock). Zagłodzenie występuje wtedy, gdy jeden lub więcej wątków zostaje zablokowanych wtedy, gdy próbują uzyskać dostęp do zasobu. Zakleszczenie jest krańcową formą zagłodzenia, występuje, gdy wątki oczekują na spełnienie warunku, który nigdy nie moŝe być spełniony. Zakleszczenie najczęściej występuje wtedy, gdy dwa lub więcej wątków oczekuje, aŝ inny (inne) wątek coś zrobi. Aby uniknąć tych sytuacji naleŝy prawidłowo zaprojektować synchronizację. Źródło: Tutorial "Java - nowy standard programowania w Internecie" Artur Tyloch