watki







body { scrollbar-face-color: #bbe6ff; scrollbar-highlight-color: #000074;
scrollbar-shadow-color: #000074; scrollbar-3dlight-color:
#4242c0; scrollbar-arrow-color: #004a74;
scrollbar-track-color: #67c2f7; scrollbar-darkshadow-color:
#4242c0 }











Wątki (threads)

Programy pisane za pomocą języków
C czy Pascal, składają się ogólnie mówiąc z pojedynczego, głównego
modułu wykonywanego linia po linii. Java podobnie jak inne nowoczesne
systemy operacyjne obsługuje wielozadaniowosć (w przypadku Javy chodzi o
wilowątkowosć, multithreading), czyli możliwosć jednoczesnego
uruchamiania oddzielnych modułów programu oraz ich dalszego równoległego
działania.
Klasyczny, jednowątkowy program
wykonywany jest przez procesor w sposób liniowy - instrukcja po insrukcji.
W przypadku wystąpienia w nim operacji, które z różnych przyczyn
przebigają relatywnie wolno (na przykład proces drukowania rysunku,
oczekiwanie na podanie przez użytkownika jakis danych), korzystniej by łoby
gdyby zostały one przesunięte na dalszy plan (były wykonywane w tle). W
tym czasie procesor zająłby się innymi, nie cierpiącymi zwłoki
zadaniami (jak na przykład wykonywaniem obliczeń czy odswierzaniem
ekranu).
Najlepszym wyjsciem byłoby
jednoczesne wykonywanie dwóch zcy nawet więcecej modułów programu (wątków).
Jeden z nich mógłby na przykład przejąć zadanie drukowania, podczas
gdy inny byłby odpowiedzialny za przyjmowanie od użytkownika danych, a
kolejny za wykonywanie odpowiednich obliczeń. Co prawda w zasadzie taka równoległosć
działań jest możliwa do uzyskania bez "wielozadaniowosci",
jednak osiągnięcie tego efektu kosztuje dosć dużo pracy. Wielowątkowosć
jest jedną z cech Javy dlatego wiele problemów związanych z tym
zagadnieniem przestaje istnieć.
Możliwosć równoległego
uruchamiania kilku wątków nie oznacza jednak jeszcze, że system wyposażony
tylko w jeden procesor rzeczywiscie będzie w stanie wykonywać więcej niż
jedno działanie jednoczesnie. De facto chodzi jedynie o wykonywanie kilku
linijek kodu danego wątku. Tak zwany scheduling (przełączanie zadań)
odpowiedzialny jest za przydzielanie każdemu wątkowi odpowiedniej ilosci
czasu obliczeniowego procesora, tak by sprawiać wrażenie rownoległosci
wykonywania.
Ponadto poszczególnym wątkom mogą
być przypisane różne priorytety, które decydują o kolejnosci
przydzielania im czasu obliczeniowego. Służy do tego funkcja "setPriority()",
której parametr musi miescić się w przedziale od MIN_PRIORITY do
MAX_PRIORITY. Im mniejsza jest jego wartosć, tym dłużej wątek będzie
czekałna swoją kolej. Wątki o takim samym priorytecie obsługiwane będą
jędnoczenie.
Istnieją dwie możliwosci
tworzenia wątków. Po pierwsze poprzez utworzenie klasy pochodnej
zawierającej kod wątku od klasy "Thread". W poniższym przykładzie
gry w kosci utworzone zostały dwa typy wątków . "WatekGracza"
obrazuje zachowanie graczy i "CzasGry" odpowiedzialny za
odmierzanie czasu trwania gry - z każdą sekundą będzie zwiększał
odpowiedni licznik. Klasa "Gra" zawiera procedurę "main()".
W pierwszej kolejnosci utworzone zostaną one uaktywnione (wywolania:
"Tomasz.start()" oraz "Jerzy.start()"). Po upływie
dziesięciu sekund, gra zostanie zakończona, a otrzymany rezultatpoddany
ocenie.

public class Gra
{
static boolean GraAktywna=true;

public static void main(String args[])
{
// tworzenie wątków (-> stan gry "New" ...)
WatekGracza Tomasz=new WatekGracza("Tomasz");
WatekGracza Jerzy=new WatekGracza("Jerzy");

//... uruchomienie wątków (-> stan gry "Runnable")
Tomasz.start();
Jerzy.start();

// uruchomienie zegara odmierzającego czas trwania gry
new CzasGry().start();
while (CzasGry.Sekundy < 10)
{// gra zakończona
GraAktywna=false;
}

// podanie wyniku
System.out.println("Punkty uzyskane przez Tomasza: "+Tomasz.Punkty+" / Jerzego: "+Jerzy.Punkty);
if (Tomasz.Punkty > Jerzy.Punkty)
System.out.println("Wygrał Tomasz");
else
System.out.println("Wygrał Jerzy");
}
}

class WatekGracza extends Thread
{
// liczba punktów osiągniętych przez gracza
public int Punkty;

// konstruktor wątku
public WatekGracza (String ImieGracza)
{
// konstruktor klasy bazowej wywoływany jest przez nazwę wątku
super(ImieGracza);
}

public void run()
{
int Rzut; // wynik rzutu
while (Gra.GraAktywna)
{
// rzut kośćmi
Rzut=(int)(Math.random()*6); // funkcja random z pakietu java.util

// zwiększenie liczby punktów o uzyskany wynik rzutu
Punkty+=Rzut;

// podanie wyniku rzutu
System.out.println(getName()+" wyrzucił "+Rzut+"oczek - liczba punktów"+Punkty);

// im gorszy wynik rzutu, tym dłużej gracz musi czekać na swoją kolej
// (jeden gracz może rzucać kilakrotnie pod rząd)
try
{
sleep ((long)(6-Rzut)*100);
}
catch (InterruptedException e)
{
// wyjątek ten zostanie usunięty kiedy bieżący potok zostanie przerwany przez inny.
// Niniejsza instrukcja catch musi tu zostać zaimplementowana,
// w przeciwnym razie kompilator zgłosi komunikat o błędzie.
System.out.println("Wyjątek");
}
}
}
}

class CzasGry extends Thread
{
static int Sekundy=0;

public void run()
{
while (Gra.GraAktywna)
{
try {sleep(1000);}
catch (InterruptedException e) {}
Sekundy+=1;
}
}
}

Metoda "run()" klasy
"WatekGracza" opisuje właściwą część gry: Każdy z graczy
może rzucać koscmi. osiągnięta liczba oczek dodawana jest do ogólnej
liczby puktów danego gracza. Im jest wyższa, tym krócej dany wątek
znajduje się w stanie "uspienia" (instrukcja "sleep()"
umożliwia ustawiene potoku w stan bezczynnosci na okreslony, podawany w
milisekundach czas). W przypadku uzyskania "6" gracz może
natychmiast rzucać dalej. Po dziesięciu sekundach sędzia (funkcja
"main()") konczy grę (ustawiając "GraAktywna = false").
Jesli utworzonych zostało kilku graczy, grają oni przeciwko sobie.
ponieważ zależnie od uzyskiwanej liczby oczek wzajemnie przenoszą się
oni w stan uspienia, każdy z nich, zależnie od uzyskiwanych rezultatów
może rzucać kilka razy pod rząd. Za każdym razem podawana jest liczba
uzyskiwanych przez gracza oczek oraz ogólna liczba zdobytych przez
niegodo tej pory punktów. Użycie funkcji "sleep()" wymusza
stworzenie metody obsługi wyjątku "InterruptedException",
ponieważ nie jest to wyjątek typu runtime-exception i jesli nie zostanie
przechwycony interpreter Javy przerwie wykonanie programu w momencie, w którym
się on pojawi. Wyjątek ten występuje w chwili, gdy jeden wątekjest
zatrzymywany, a uaktywniany inny (przełączanie zadań).
Identyfikacja gracza odbywa się za
posrednictwem nazwy odpowiadającego mu wątku. Konstruktor klasy "WatekGracza"
otrzymuje tę nazwę jako parametr. Za posrednictwem wywołania
"super()" przewkazuje ją dalej, do konstruktora klasy bazowej.
Z kolei "getName()" podaje nazwę potoku, który jest aktualnie
aktywny.
Druga możliwosć polega na
utworzeniu klasy wątku implementującej interfejs "Runnable".
Każda klasa, która implementuje ten interfejs musi implementować własną
metodę "run()". W celu uruchomienia takiego wątku należy więc
najpierw stworzyć obiekt tej klasy, a potem utworzyć wątek (obiekt
klasy "Thread"). Konstruktor klasy "Thread", któremu
należy przekazać nasz obiekt i staje sie on odpowiedzialny za
przeprowadzenie wszelkich koniecznych inicjalizacji. Na koniec w celu
uruchomienia wątku należy wywołać metodę "start()".
Definicja przykładowego wątku może więc wyglądać następująco:

class DemoThread implements Runnable
{
public void run() // zadaniem wątku jest odliczenie od 1 do 10
{
int i;
for (i=0; i<10; ++i)
System.out.println(i);
}
}

public class Demo
{
public static void main (String args [])
{
// utworzenie obiektu klasy "DemoThread"
DemoThread d = new DemoThread();

// utworzenie wątku z tego właśnie obiektu
Thread t1 = new Thread (d);
// a następnie jego wystartowanie
t1.start();
}
}

Oprócz metody "run()" w
interfejsie "Runnable" istnieją jeszcze inne -"init()",
"start()" czy "stop()", które mogą mieć istotne
znaczenie dla uruchomienia bądź zakończenia wątku. Jak widać w
podanym przykładzie wątek nie musi ich implementować - wystarczy wywołać
odpowiednią metodę klasy bazowej. Metoda "init()" przeprowadza
wszystkie konieczne do uruchomienia wątku inicjalizacje (na przykład:
otwarcia okna, załadowanie fontu, przypisanie priorytetu itd.). wywołanie
metody "start()" uruchamia wątek, a "stop()"zatrzymuje
go.
Zależnie od tego, czy wątek został
własnie stworzony, czy jest aktywny, zatrzymany, czy też zakończony możemy
rozróżnić kilka następujących stanów:


New
Wątek, ewentualnie obiekt jest
tworzony za pomocą operatora "new", ale nie oznacza to jego
natychmiastowego uruchomienia (stan przed wywołaniem metody
"start()"). W tym stanie wątek nie zużywa jeszcze żadnnych
zasobów systemu.


Runnable
Wywołanie metody
"start()" wątku nie oznacza jeszcze, że wykonuje się on ciągle.
Jesli (na przykład) w tym samym czasie aktywny jest jakis inny wątek,
to inne przechodzą do stanu "Runnable". W każdym momencie
może on zostać uaktywniony.


Running
Wątek jest wykonywany (otrzymał
czas obliczeniowy procesora).


Not Runnable
W tym stanie dany wątek
nie może zostać wystartowany. W praktyce oznacza to, że albo
oczekuje na wykonanie jakiejs wolnej operacji wejscia/wyjscia (na
przyklad na podanie przez użytkownika danych z klawiatury) albo też
został przeniesiony w ten stan za pomocą odpowiednich metod:
"suspend()", "sleep()" lub "wait()". Wątek
znajdujący się w tym stanie może zostać przeniesiony do stanu
"Runnable" w monencie zakończenia wstrsymującej go operacji
wejscia/wyjscia, lub też po wywołaniu metody "notify()" lub
"notifyAll()" (jesli zostrał wstrzymany przez
"suspend()", "resume()" lub "wait()").


Dead
Wątek może zostać zatrzymany
za pomocą metody "stop()". Po takiej operacji wątek
formalnie może jeszcze istnieć, o ile tylko obiekt klasy Thread jest
jeszcze aktywny. Z tego stanu wątek nie może być już wystartowany -
jest więc "martwy" ("dead"). W stan ten wątek
zostaje także przeniesiony po normalnym jego wykonaniu, a następnie
zakończeniu.


Tworzenie programów wspólbieżnych,
jednoczesnie korzystających z zasobów systemowych, konkurujących o dostęp
do urządzeń zewnętrznych moze powodować wiele nieoczekiwanych i
trudnych do rozwiązania problemów. Pierwszy z nich pojawia się już w
momencie, kiedy kilka równolegle wykonywanych wątków zechce użyć tych
samych pól danych. Wykonywaine watków jest cyklicznie przerywane, ale w
nieustalonych i nierównych odstępach czasu. Nie wolno zatem z góry zakładać,
że jakis inny wątek nie będzie wykonywany równolegle z naszym i że w
czasie potrzebnym do wykonania fragmentu wątku inny nie zmodyfikuje
jakichs współdzielonych danych. Problem ten może przybliżyć
przedstawiony poniżej przykład:
Klasa "PutGetClass"
zajmuje się zarządzaniem współdzielonego przez dwa wątki bufora.
Licznik o nazwie "counter" podaje kolejną wolną pozycję w
buforze. Metoda "Put()" umieszcza na wskazywanej przez licznik
pozycji jego obecną wartosć i zwiększa licznik o jeden. Za pomocą
metody "Get()" następuje odczytanie wskazywanej przez licznik
pozycji bufora, a on sam jest następnie pomniejszany o jeden. Tak własnie
chcielibysmy, aby zachowywała się ta klasa. jesli jednak za pomocą
metody "Put()" zostanie wykonany zapis wartosci do bufora, ale
scheduler (proces zarządzający zadaniami) przełączy następnie
zadania, to stan licznika pozostanie niezmieniony. Zwiększy się o 1
dopiero w chwili, gdy procesor zacznie ponownie wykonywać wątek. Jesli
metoda "Put()" umieszczać będzie w buforze zamiast wartosci
licznika przekazywany argument, to klasa ta będzie odpowiadać normalnej
pamięci FIFO. Jednakże zapisywanie stanu licznika wydatnie pomaga w
zobrazowaniu błędów prrzyporządkowania.
Klasa "PutGetUser"
definiuje wątki, korzystające intensywnie z operacji zapisu
("Put()") oraz odczytu ("Get()") do bufora. Pętla
"for" ma za zadanie symulację wykonywania w międzyczasie
innego, krótkiego programu.

class PutGetClass
{
static int counter = 0;
static int buffer[] = new int[32];

static synchronized void Put()
// static void Put()
{
buffer[counter] = counter;
++counter;
}

static synchronized int Get()
// static int Get()
{
--counter;
if (buffer[counter] != counter)
System.out.println("Błąd przypisania !! "+buffer[counter]+" "+counter);
return (buffer[counter]);
}
}

class PutGetUser extends Thread
{
public void run()
{
while (true)
{
PutGetClass.Put();
int i;
for (i=0; i<10; ++i);
PutGetClass.Get();
}
}
}

public class Demo
{
public static void main (String args[])
{
new PutGetUser().start();
new PutGetUser().start();
}
}

Jesli klasa "PutGetClass"
zostanie zostanie wykorzystana w programie jednowątowym, wszystko będzie
przebiegało zgodnie z oczekiwaniami i na ekranie nie pojawi się nic
niespodziewanego. Jednak w programie wielowątkowym po jakims czasie
ujrzelibymy serię komunikatów o blędach.
Rozwiązaniem powstałego problemu
jest "synchronizacja". W momencie wywołania krytycznej metody i
uruchomienia procedur, jakie ona zawiera, niedozwolone jest przełączanie
na inny wątek, który nawet przypadkowo miałby ją również wywołać.
Java wyposarzona jest w mechanizm, który w bardzo dokłądny sposób
sytuację taką potrafi rozpoznać. Z tego względu wszystkie metody, które
stanowią podobne zagrożenie, Poprzedzone zostają słowem kluczowym
"synchronized". Jeżeli teraz wspomniana metoda zostanie wywołana
przez którys z wątków, jest automatycznie zablokowana dla innych wątków.
Dostęp do niej odblokowywany jest dopiero w momencie, kiedy poprzedni wątek
przestaje z niej korzystać. Jeżeli w czasie, kiedy dana metoda
zablokowana jest przez jeden wątek, a jakis inny spróbuje ją wywołać,
to zostaje przeniesiony w stan oczekiwania - do czasu zwolnienia dostępu
do potrzebnej metody. Zarządzaniem blokowania i zwalniania dostępu do
metod zajmują się tzw. "monitory". W powyższym przykładzie
obie metody: "Get()" oraz "Put()" powinny być
zadeklarowane jako synchronizowane.
Oczywistym jest chyba fakt, że
opisane powyżej problemy będą występować raczej rzadko.Błędy tego
typu są też dosć trudne do zlokalizowania. W przypadku wspólnych
obszarów danych, używanych przez więcej niz tylko jeden wątek, powinno
się więc postępować dosć ostrożnie (tzn. definiować jako
"synchronized" wszystkie metody, które mogą się znaleźć w
opisanej powyżej sytuacji) lub unikać ich - w miarę możliwosci.
Synchronizacja nie jest niestety
srodkiem doskonałym - kryje ona w sobie niebezpieczeństwo wzajemnego
blokowania metod. Jesli jeden wątek zablokuje metodę (nazwijmy ją A), a
wykonywany współbierznie wątek inną (o nazwie B), to jesli metoda B (w
danej chwili zablokowana) wywoływana jest przez metodę A, a jesli
jednoczesnie metoda A próbuje wywołać B, to dochodzi to tzw.
zakleszczenia. W takiej sytuacji bowiem oba wątki czekają na siebie
nawzajem, czyli na zakończenie korzystania z danej metody przez
"swojego przeciwnika". Ponieważ jednak nie dojdzie do tego
nigdy (zakończenie metody A wymaga wykonania zablokowanej metody B, a
odblokowanie B warunkowane jest wczesniejszym zakończeniem A), program
wpada w sytuację bez wyjscia, okreslanej z angielska jako
"deadlock". Praktycznie istnieje tylko jedna możliwosć
powstawania tego typu sytuacji: należy unikać jak ognia definiowania
metod jako "synchronized", korzystających z innych tego typu
metod.
Stosowanie metod
"synchronized" jest de facto wykroczeniem przeciwko
wielozadaniowosci - silnie faworyzuje wykonujący taką metodę wątek.
Rozsądne ograniczenie definiowania metod synchronizowanych do niezbędnego
minimum pozwala uniknąć faworyzowania któregos z wątków i ustrzec się
przed niebezpieczeństwem zakleszczeń. Dlatego też nie powinny być
implementowane jako "synchronized"na przykład obliczenia, które
zajmują wiele czasu. Ponadto z wątków powinno się robić użytek
jedynie wówczas, kiedy faktycznie konieczne jest zastosowanie równoległego
wykonywania kodu programu. Sytuacja ma miejsce jedynie wówczas, gdy każdy
wątek ma do wykonania scisle okreslone zadanie -wówczas z reguły nie
występuje tez i ich wzajemna zależnosć.
powrót






Wyszukiwarka

Podobne podstrony:
wątki ceglane pśin
SO 05 Watki
watki
watki
opis watki (2)
Wątki mitologiczne w sztuce nowożytnej
opis watki
Java watki
sołtys,systemy operacyjne, wątki
wyk watki mutexy
watki 2
wątki
Wątki
Wątki
watki

więcej podobnych podstron