Wątek
jest pojedynczym sekwencyjnym strumieniem sterowania wewnątrz programu. Mówiąc o wątkach i
ich wzajemnych relacjach bierze się pod uwagę: kto je stworzył, kiedy powinny zostać uruchomione lub
zatrzymane, jaką funkcje realizują, etc.
Metody tworzenia wątków.
Istnieją dwie metody tworzenia wątków:
1.Utworzyć podklasę klasy
Thread
, przysłaniając metodę run własną metodą, a następnie utworzyć składnik tej
nowej podklasy i wywołać metodę run.
class A extends Thread {
public A(String name) {super(name);}
System.out.println(„My name is ” + getName());
}
}
class B {
public static void main(String[] args) {
A a = new A(„mud”);
a.start();
}
}
2. Zaimplementować interfejs (uruchomieniowy)
Runnable
w klasie z publiczną metodą
run
, stworzyć
składnik tej klasy, przekazać referencje do tego obiektu do konstruktora
Thread.
Zaletą jest tu możliwość
rozszerzenia (
extends
) klas zdefiniowanych przez użytkownika.
class C extends .... implements Runnable {
public void run() {
System.out.println(„Wątek o nazwie ” + Thread.currentThread().getName());
}
}
class B {
public static void main(String[] args) {
C c = new C();
Thread t = new Thread(c, „mud too”);
t.start();
}
}
Możliwe stany wątków:
tworzony (new), gotowy (runnable), wykonywany (running), zawieszony (suspended), zablokowany (blocked),
zawieszony-zablokowany (suspended-blocked), zakończony (dead). Przejście pomiędzy stanami możliwe jest
dzięki wywołaniu którejś z funkcji:
* yield, sleep
(metody statyczne w klasie Thread, stosują się więc tylko do bieżąco wykonywanego wątku);
*
resume,
stop, suspend, start, join
(metody składowe (instancyjne), które mogą wywołać dowolne wątki
na dowolnym obiekcie typu
Thread
);
*
wait
(może być bez parametrów i z parametrami: (
long milisec
) oraz (
long milisec, int nanosec
)),
notify
,
notifyAll
(muszą być wywoływane z wnętrza bloku
synchronized
).
wait(), wait(timeout)
,
wait(timeout,nanoseconds)
–milisec,nanosec.
Tabela stanów wątków.
Current
New State
public void run() {
State
runnable
running
suspended blocked
suspended-
blocked
dead
new
start
runnable
scheduled
suspend
stop
running
time slice
ends,
yield
suspend
blocking
IO,
sleep
,
wait
,
join
stop
,
run ends
suspended
resume
stop
blocked
IO
completes,
sleep
expires,
notify
,
notifyAll
,
join
completes
suspend stop
suspenden-
blocked
IO
completes
resume
stop
W Javie nowo tworzony wątek dziedziczy priorytet wątku, który go stworzył. Wątki obdarzane są priorytetami,
w zakresie od
MIN_PRIORITY
do
MAX_PRIORITY
(stałe zdefiniowane w klasie
Thread
, standardowo w
Javie priorytety mają wartości od 1 do 10, przy czym normalnie dla wątków 5). Standardowo wątkom
przyznawany jest priorytet
NORM_PRIORITY
, ale można też użyć
setPriority, getPriority
, aby go zmienić.
public class TestPriorytetow {
public static void main(String argv[]) {
C c = new C();
Thread t1 = new Thread(c, "pierwszy watek");
Thread t2 = new Thread(c, "drugi watek");
t2.setPriority(t1.getPriority()+1);
t1.start();
t2.start();
}
W każdej chwili, gdy wiele wątków gotowych jest do działania, runtime system wybiera ten wątek (runnable)
który ma najwyższy priorytet i go wykonuje. Tylko w przypadku, gdy działający wątek zatrzymuje się (po
stop
), oddaje sterowanie (po
yield
), lub przestaje być wątkiem działającym (not runnable), wątek o niższym
priorytecie zacznie się wykonywać. W przypadku wątków o tym samym priorytecie, scheduler wybiera jeden z
nich na zasadzie round-robin. Wybrany wątek będzie działał dopóty, dopóki nie stanie się prawdziwe co
najmniej jedno ze zdań:
• Wątek o wyższym priorytecie staje się gotowym do wykonania (runnable).
• Bieżący wątek oddaje sterowanie (po
yield
) lub jego metoda
run
się kończy.
• W systemie w podziałem czasu skończy się okres przydzielony dla wątku
Gdy któreś z powyższych zdań okaże się prawdziwe, wtedy zacznie działać wątek o niższym priorytecie.
Jednak reguła ta może okazać się fałszywa, gdy scheduler został tak zaimplementowany, aby nie doprowadzać
do zagłodzenia wątków o niskich priorytetach.
Uwaga: przy pracy z wątkami należy pamiętać o ich synchronizacji i zabezpieczeniu zmiennych
współdzielonych. Zobacz opis do słów:
volatile
,
synchronized
.
Podział czasu.
Standardowo procesor przydzielany jest wątkom na okres 100 ms. Dzieje się tak jednak tylko dla
Windows95/NT. W przypadku JDK1.1 dla systemu Solaris 2.x nie było zaimplementowanego podziału czasu.
Aby sprawdzić, z jakim schedulerem ma się do czynienia, wystarczy uruchomić następujący przykład:
class C extends .... implements Runnable {
public void run() {
System.out.println(„Wątek o nazwie ” + Thread.currentThread().getName());
// aby zapewnic przełaczanie wątków niezależnie od schedulera można w tym
miejscu wywołać
// Thread.yield(); lub równoważnie Thread.currentThread().yield();
}
}
public class TestScheduleraWatkow {
public static void main (String argv[]) {
DzialajacyWatek dw = new DzialajacyWatek();
new Thread(dw, "pierwszy watek").start();
new Thread(dw, "drugi watek").start();
new Thread(dw, "trzeci watek").start();
}
}
Jeśli na ekranie wyświetlany będzie cały czas komentarz „Wątek o nazwie pierwszy wątek”, znaczy to, że nie
ma przełączania wątków. W takim przypadku można zaimplementować własny scheduler, który uruchamiany
będzie z najwyższym priorytetem i który będzie dokonywał przełączeń pomiędzy procesami wykorzystując
metodę
nup()
. Wątek taki powinien być uruchomiony przed wszystkimi innymi wątkami oraz powinien
wywoływać metodę
setDeamon(true)
.
public class Scheduller implements Runnable {
private int timeSlice = 0; // milliseconds
private Thread t = null;
public Scheduller(int timeSlice) {
this.timeSlice = timeSlice;
t = new Thread(this);
t.setPriority(Thread.MAX_PRIORITY);
}
public void run() {
int napping = timeSlice;
while (true) {
try{
t.sleep(napping);
} catch(InterruptedException e){}
}
}
}
Poniżej zamieszczony jest kolejny przykład na uruchamianie wątków. W przykładzie tym przechwytywany jest
wyjątek, wykorzystana jest metoda
join()
oraz
currentThread()
.
public class TestUruchamianiaWatkow {
public static void main (String argv[]) {
C c = new C();
while (true){
t.setDaemon(true); t.start();
Thread t = new Thread(c, “wątek pierwszy”);
System.out.println("operator new Thread() wykonany" + (t == null ? "blednie" ;
"poprawnie") + ".");
t.start();
try {
t.join(); // czeka aż wątek zakończy wykonywanie metody run()
}
catch (InterruptedException ignored){}
}
}
}
Jeden wątek może przerwać inny wątek przez wywołanie metody składowej
interrupt
przerywanego wątku.
Jeśli przerywany wątek jest w zablokowany po
sleep
,
join
lub
wait
, metody te wysyłają obiekt
InterruptedException
zamiast się normalnie kończyć. Jeśli wątek nie jest zablokowany, to ustawia się po jego
przerwaniu odpowiednia logiczna flaga. Flagę można sprawdzić metodą składową
isInterrupted
(zwracającą
logiczną wartość). Dla wygody w klasie
Thread
zamieszczono statyczną metodę
interrupted,
która wywołuje
currentThread().isInterrupted()
. Wywołanie to resetuje flagę (czego nie robi
isInterrupted
).
Do sprawdzania „żywotności” wątków służą metody
isDeamon
,
isAlive
,
getName. isAlive
zwraca
false
dla
nowego wątku albo wątku zakończonego (dead thread), zwraca
true
dla wątków ruchomionych metodą start i
nie zatrzymanych (tj. wątków gotowych do działania lub jak i wątków niegotowych (runnable or not runnable),
przy czym nie można rozróżnić wątku nowego od skończonego jak również wątku gotowego od wątku
niegotowego. Ponadto do oceny stanu wątków można posłużyć się mechanizmem wyjątków. W poniższym
przykładzie przechwytywany jest wyjątek
ThreadDeath.
public class TestZatrzymania {
public static void main(String argv[]) {
Thread t = new Thread(new DzialajacyWatek());
try {
t.start();
metodaMogacaZatrzymacWatek();
} catch (ThreadDeath aTD) {
// tu jest miejsce na zareagowanie na przerwanie watku
throw aTD; // przekaz blad dalej
}
}
Wątki można grupować w pewne zbiory. Do tworzenia zbiorów wyjątków służy klasa
ThreadGroup
.
Dostęp do pamięci.
Zgodnie z definicją języka Java, wpisywanie danych do komórek pamięci nie musi
odbywać się w kolejności, w jakiej zadeklarowane to zostało w programie. Aby taką kolejność zachować,
deklaruje się nadpisywaną przez wątki zmienną słowem
volatile
class D {
static int x = 0, y = 0;
static void a() {x = 3, y = 4; }
static int b() {int z = y; z += x; return z;}
}
Jeśli różne wątki wywołają
a()
i
b(),
to zwracane wartości przez
b
mogą być następujące: 0, 3, 4, 7 (gdzie 4 jest
zwrócone, jeśli
a
zapisze w
y
wartość 4 i będzie to widoczne dla
b
wcześniej niż zapis wartości 3 w
x
. Jeśli
obie zmienne byłyby zadeklarowane jako
static volatile int x =0, y =0;
to
b
nigdy nie zwróciłoby 4.
Aktywne czekanie:
sensownie
raczej źle
while (buffer[putIn].occupied)
Thread.currentThread().yield();
while (buffer[putIn].occupied);
Można też aktywne czekanie zaimplementować następująco
while (condition) try {wait();} catch (InterruptedException e) {}
i używać
notifyAll
.
Semafory.
W standardzie Javy nie wyróżniono semaforów. Dlatego trzeba je symulować przy użyciu
monitorów (które mieszczą się już w standardzie tego języka). Pewną „namiastką” semaforów binarnych jest
blok synchronizacji, służący zabezpieczaniu sekcji krytycznej.
Przykład
synchronized (obj) {
critical section
}
obj
może tu być dowolnym obiektem.
Jeśli wszystkie sekcje krytyczne znajdują się w metodach pojedynczego obiektu, wtedy blok synchronizacji
używa
this
(referencje do tego obiektu)
synchronized (this) { ... }
Ponadto, jeśli ciało metody stanowi blok synchronizacji z
this
, tj.
type
method
( ... ) { synchronized (this) {
critical section
} }
kompilator Javy pozwala na użycie zapisu
synchronized type method ( ... ) {
critical section
}
Jeśli wzajemne wykluczanie dotyczy wielu różnych klas bądź obiektów, wtedy obiekt wspólny dla
synchronizowanych bloków musi zostać stworzony na zewnątrz klasy i musi być przekazany do ich
konstruktorów.
private Object mutex = null;
....
mutex = this ;
....
public void run(){
for ( int m = 1; m <= M; m++)
synchronized (mutex) { sum = fn (sum, m); } }
mutex
traktowany jest tu jako
zmienna warunkowa, na której
dokonuje się podnoszenia i
opuszczania.
Uwaga: semafory binarne można używać do wzajemnego wykluczania jak i do synchronizacji procesów.
Użycie bloku synchronizacji umożliwia tylko to pierwsze.
synchronized – zabezpiecza przed jednoczesnym wykonaniem fragmentu kodu zawierającego dany obiekt
przez współbieżnie działające wątki.
// zabezpieczenie przypisania w metodzie
względem obiektu
TryPointPrinter
public class TryPointPrinter {
public void print (Point p) {
float safeX, safeY;
synchronized(this) {
safeX = p.x();
safeY = p.y();
}
System.out.println("x =", + safeX + ", y=" + safeY );
}
}