java_watki.pdf

(276 KB) Pobierz
Microsoft Word - roz_4.doc
Jacek Rumi ń ski - J ę zyk JAVA – Rozdzia ł 4
Rozdział 4 Programowanie współbieżne – wątki
4. 1 Rys historyczny
4.2 Tworzenie w ą tków w Javie
4.3 Priorytety
4.4 Przetwarzanie wspó ł bie ż ne a równoleg ł e
4.5 Przerywanie pracy w ą tkom
4.6 Przerywanie tymczasowe
4.7 Synchronizowanie w ą tków
4.8 Grupy w ą tków – ThreadGroup
4.9 Demony
Bibliografia
4.1 Rys historyczny
Na początku człowiek stworzył maszynę. Dalsza historia rozwoju technologii to
próby uzyskania jak największej efektywności działania maszyny. Stwierdzenie to
jest również słuszne dla komputera - maszyny matematycznej.
W pierwszym okresie użytkowania komputerów, celem zwiększenia ich efektywności
działania, stosowano programowanie wsadowe. W podejściu takim, programy
wykonywały się cyklicznie: jeden po drugim. Komputer stał się maszyną
wielozadaniową wykonującą różne operacje, np. liczył całkę, po czym rozwiązywał
model fizyczny zjawiska, następnie obliczał wymaganą grubość pancerza czołgu, itd.
Ponieważ typy zadań są często różne, wymagają odmiennych zasobów komputera,
dlatego opracowano rozwiązanie umożliwiające dzielenie zasobów komputera
pomiędzy poszczególnych użytkowników. W ten sposób każdy użytkownik
otrzymywał prawo wykonania swojego programu na komputerze. Rozwiązanie to
było niezwykle istotne w przypadku konieczności wykonania dwóch zadań, skrajnie
obciążającego czasowo komputer oraz mało obciążającego czasowo komputer, np.
policzenie parametrów skomplikowanego (wielowymiarowego) modelu i rozwiązanie
układu równań. Wielozadaniowość nie stanowiła jednak wystarczająco efektywnego
rozwiązania. Zaproponowano więc możliwość podziału zadań (programów) na
mniejsze funkcjonalnie spójne fragmenty kodu wykonywanego (odseparowane
strumienie wykonywania działań) zwane wątkami (threads). Wątek jest więc
swoistym podprogramem (zbiorem wykonywanych operacji) umożliwiającym jego
realizację w oderwaniu od innych wątków danego zadania. Relacja pomiędzy
wątkami określana głównie poprzez dwa mechanizmy: synchronizację i zasadę
pierwszeństwa. Synchronizacja wątków jest niezwykle istotnym zagadnieniem,
szczególnie wówczas gdy są one ze sobą powiązane, np. korzystają z wspólnego
zbioru danych. Określanie zasad pierwszeństwa - priorytetów jest pomocne
wówczas, gdy efekt działania danego wątku jest o wiele bardziej istotny dla
użytkownika niż efekt działania innego wątku. Oczywiście wątki mogą być
generowane nie tylko jawnie przez użytkownika komputera ale również niejawnie
poprzez wywołane programy komputerowe. Przykładowo dla Maszyny Wirtualnej
Javy działa co najmniej kilka wątków: jeden obsługujący kod z metodą main (), drugi
obsługujący zarządzanie pamięcią (GarbageCollection) jeszcze inny zajmujący się
odświeżaniem ekranu, itp. Tworzenie wielu wątków jest docelowo związane z
możliwością ich równoczesnego wykonywania (programowanie równoległe). Możliwe
jest to jednak jedynie w systemach wieloprocesorowych. W większości obecnych
komputerach osobistych i stacjach roboczych współbieżność wątków jest
emulowana. Stosowanie podziału kodu na liczne wątki (procesy danego programu-
4-3
Jacek Rumi ń ski - J ę zyk JAVA –
Jacek Rumi ń ski - J ę zyk JAVA – Rozdzia ł 4
zadania) ma więc charakter zwiększenia efektywności działania (głównie w
systemach wieloprocesorowych) oraz ma charakter porządkowania kodu (głównie w
systemach jednoprocesorowych - podział zadań).
Programy Javy (zarówno aplety jak i aplikacje) są ze swej natury wielowątkowe.
Świadczy o tym choćby najprostszy podział na dwa wątki: wątek obsługi kodu z
metodą main () (dla aplikacji) oraz wątek zarządzania stertą GarbageCollection
(posiadający znacznie niższy priorytet). Oczywiście programista tworząc własną
aplikację może zapragnąć podzielić przepływ wykonywania działań w programie na
szereg własnych wątków. Java umożliwia dokonanie takiego podziału.
4.2 Tworzenie wątków w Javie
W celu napisania kodu wątku w Javie konieczne jest albo bezpośrednie
dziedziczenie z klasy Thread lub pośrednie poprzez implementację interfejsu
Runnable . Wykorzystanie interfejsu jest szczególnie istotne wówczas, gdy dana
klasa (klasa wątku) dziedziczy już z innej klasy, a ponieważ w Javie nie może
dziedziczyć z dwóch różnych klas, dlatego konieczne jest zaimplementowanie
interfejsu. Każdy wątek powinien być wywołany, mieć opisane zadania do wykonania
oraz posiadać zdolność do zakończenia jego działania. Funkcjonalność tą, otrzymuje
się poprzez stosowanie trzech podstawowych dla wątków metod klasy Thread :
start () - jawnie wywołuje rozpoczęcie wątku,
run() - zawiera zestaw zadań do wykonania,
interrupt () - umożliwia przerwanie działania wątku.
Metoda run () nie jest wywoływana jawnie lecz pośrednio poprzez metodę start ().
Użycie metody start () powoduje wykonanie działań zawartych w ciele metody run ().
Jeśli w międzyczasie nie zostanie przerwane zadanie, w ciele którego dany wątek
działa, to końcem życia wątku będzie koniec działania metody run (). Do innych
ważnych metod opisujących działanie wątków należy zaliczyć:
static int activeCount ():
- wywołanie tej metody powoduje zwrócenie liczby wszystkich aktywnych
wątków danej grupy,
static int enumerate (Thread[] tarray):
- wywołanie tej metody powoduje skopiowanie wszystkich aktywnych wątków
danej grupy do tablicy oraz powoduje zwrócenie liczby wszystkich
skopiowanych wątków,
static void sleep (long ms); gdzie ms to liczba milisekund:
- wywołanie tej metody powoduje uśpienie danego wątku na czas wskazany
przez liczbę milisekund;
static yield ():
- wywołanie tej metody powoduje przerwanie wykonywania aktualnego wątku
kosztem wykonywania innego wątku (jeśli taki istnieje) na Maszynie
Wirtualnej. Metodę tą stosowaliśmy omawiając proces GarbageCollection,
zawieszając wątek działania programu na rzecz wywołania wątku zwalniania
pamięci ( System . gc ()).
4-4
Jacek Rumi ń ski - J ę zyk JAVA –
Jacek Rumi ń ski - J ę zyk JAVA – Rozdzia ł 4
Ponadto istnieją jeszcze inne metody klasy Thread , np. setName (), setDaemon (),
setPriority (), getName (), getPriority (), isDaemon (), isAlive (), isInterrupt (), join (), itd.,
które mogą być pomocne w pracy z wątkami. Część z tych metod zostanie poniżej
zaprezentowana.
W celu zobrazowania możliwości generacji wątków w Javie posłużmy się
następującym przykładem:
Przykład 4.1:
//Los.java:
import java.util.*;
class Jasnosc extends Thread {
Thread j;
Jasnosc(Thread j){
this.j=j;
}
public void run(){
int n=0;
boolean b = false;
Random r = new Random();
do{
if( !(this.isInterrupted())){
n = r.nextInt();
System.out.println("Jasność");
} else {
n=200000000;
b=true;
}
}while(n<200000000);
if(b){
System.out.println(this+" jestem przerwany, kończę pracę");
} else {
Thread t = Thread.currentThread();
System.out.println("Tu wątek "+t+" Jasność");
System.out.println("Zatrzymuję wątek: "+j);
j.interrupt();
System.out.println("KONIEC: Jasność");
}
}
}// koniec class Jasnosc
}
public void run(){
int n=0;
Random r = new Random(12345678L);
boolean b = false;
4-5
Jacek Rumi ń ski - J ę zyk JAVA –
class Ciemnosc extends Thread {
Thread c;
public void ustawC(Thread td){
this.c=td;
Jacek Rumi ń ski - J ę zyk JAVA – Rozdzia ł 4
do{
if( !(this.isInterrupted())){
n = r.nextInt();
System.out.println("Ciemność ");
n=200000000;
b=true;
}
}while(n<200000000);
if(b){
System.out.println(this+" jestem przerwany, kończę pracę");
} else {
if (c.isAlive()) {
Thread t = Thread.currentThread();
System.out.println("Tu wątek "+t+" Ciemność");
System.out.println("Zatrzymuję wątek: "+c);
c.interrupt();
} else {
Thread t = Thread.currentThread();
System.out.println("Tu wątek "+t+" jestem jedyny ");
}
System.out.println("KONIEC: Ciemność");
}
}
}// koniec class Ciemnosc
public class Los{
public static void main(String args[]){
Ciemnosc zlo = new Ciemnosc();
Jasnosc dobro = new Jasnosc(zlo);
zlo.ustawC(dobro);
zlo.start();
dobro.start();
}
}// koniec public class Los
Przykładowy rezultat działania powyższego programu może być następujący:
Ciemność
Jasność
Ciemność
Jasność
Ciemność
Tu wątek Thread[Thread-1,5,main] Jasność
Ciemność
Zatrzymuję wątek: Thread[Thread-0,5,main]
Ciemność
KONIEC: Jasność
Thread[Thread-0,5,main] jestem przerwany, kończę pracę
4-6
Jacek Rumi ń ski - J ę zyk JAVA –
} else {
Jacek Rumi ń ski - J ę zyk JAVA – Rozdzia ł 4
W prezentowanym przykładzie stworzono dwie klasy dziedziczące z klasy Thread .
Obie klasy zawierają definicję metody run () konieczną dla pracy danego wątku.
Metody run () zawierają generację liczb pseudolosowych w pętli, przerywanej w
momencie otrzymania wartości progowej (=> 200 000 000). Dodatkowo
wprowadzono w pętli do - while () obu metod warunek sprawdzający czy dany wątek
nie został przerwany poleceniem interrupt (). Jeżeli wygenerowana zostanie liczba
przekraczająca wartość progową to dany wątek zgłasza kilka komunikatów oraz
przerywa pracę drugiego. W programie umieszczono kontrolę działania wątków
( isAlive() - zwraca true , jeżeli dany wątek wykonuje jeszcze działanie zdefiniowane w
jego metodzie run()), gdyż koniec wykonywania metody run() danego wątku jest
równoważne z jego eliminacją. Nie można wówczas otrzymać informacji o
działającym wątku poprzez domyślną konwersję „uchwytu” obiektu metodą toString ()
w ciele instrukcji System . out . println (). Jeśli wątek pracuje wówczas można uzyskać o
nim prostą informację zawierającą dane o jego nazwie (np. Thread-0), priorytecie
(np. 5) oraz o nazwie wątku naczelnego (np. main ): np.: Thread [ Thread-0,5,main ].
Efekt działania powyższego programu może być różny ponieważ wartość zmiennej
sprawdzanej w warunku pętli jest generowana losowo.
Kolejny przykład ukazuje możliwość nadawania nazw poszczególnym wątkom oraz
ustawiania ich priorytetów:
Przykład 4.2:
//Widzenie.java:
//Kod klas Ciemnosc i Jasnosc musi byś dostępny dla klasy Widzenie,
//umieszczony np. w tym samym katalogu (ten sam pakiet).
import java.util.*;
public class Widzenie{
public static void main(String args[]){
System.out.println("Maksymalny priorytet wątku = "+Thread.MAX_PRIORITY);
System.out.println("Minimalny priorytet wątku = "+Thread.MIN_PRIORITY);
System.out.println("Normalny priorytet wątku = " + Thread.NORM_PRIORITY+"\n");
}
Ciemnosc zlo = new Ciemnosc();
Jasnosc dobro = new Jasnosc(zlo);
zlo.setName("zlo");
zlo.setPriority(4);
dobro.setName("dobro");
dobro.setPriority(6);
zlo.ustawC(dobro);
zlo.start();
dobro.start();
}// koniec public class Widzenie
4-7
Jacek Rumi ń ski - J ę zyk JAVA –
Zgłoś jeśli naruszono regulamin