ready
running
dead joining
start
yield
sleeping
sleep
// ... time over
// ... completed
join
publi lass Join implements Runnable {
private stati int ount = 0;
private int n = ount++;
private stati Thread[℄ task = new Thread[3℄;
publi void run() {
try {
if (n>0) {
task[n-1℄.join();
System.out.println( "T hre ad- "+ n+" joined Thread-"+(n-1));
}
} ath (InterruptedExep ti on e) {
System.err.println( e.t oS tri ng( )) ;
}
} ...
publi stati void main(String[℄ args) {
for(int i=0; i<3; i++)
task[i℄ = new Thread(new Join());
for(int i=0; i<3; i++)
task[i℄.start();
}
} // end of lass Join
... liefert:
> java Join
Thread-1 joined Thread-0
Thread-2 joined Thread-1
Spaÿeshalber betrahten wir noh eine kleine Variation des letzten
Programms:
private stati int ount = 0;
private int n = ount++;
private stati Thread[℄ task = new Thread[3℄;
publi void run() {
try { task[(n+1)%3℄.joi n() ; }
ath (InterruptedExep tio n e) {
System.err.printl n(e .to St rin g( ));
}
}
publi stati void main(String[℄ args) {
for(int i=0; i<3; i++)
task[i℄ = new Thread(new CW());
for(int i=0; i<3; i++) task[i℄.start();
}
•
Jeder Thread geht in einen Wartezustand (hier: joining) über und wartet auf einen anderen Thread.•
Dieses Phänomen heiÿt auh Cirular Wait oder Deadlok eineunangenehme Situation, die man in seinen Programmen
tunlihst vermeiden sollte.
•
Damit Threads sinnvoll miteinander kooperieren können, müssen sie miteinander Daten austaushen.•
Zugri mehrerer Threads auf eine gemeinsame Variable istproblematish, weil niht feststeht, in welher Reihenfolge die
Threads auf die Variable zugreifen.
•
Ein Hilfsmittel, um geordnete Zugrie zu garantieren, sind Monitore.... ein Beispiel:
private stati int x = 0;
private stati void pause(int t) {
try {
Thread.sleep((in t) (Math.random()*t*10 00 ));
} ath (InterruptedExep ti on e) {
System.err.print ln( e.t oS tri ng( )) ;
}
}
publi void run() {
String s = Thread.urrentThrea d() .g etN ame () ;
pause(3); int y = x;
System.out.printl n( s+ " read "+y);
pause(4); x = y+1;
System.out.printl n( s+ " wrote "+(y+1));
publi stati void main(String[℄ args) {
(new Thread(new In())).start();
pause(2);
(new Thread(new In())).start();
pause(2);
(new Thread(new In())).start();
}
} // end of lass In
•
publi stati Thread urrentThread(); liefert (eine Referenz auf) das ausführende Thread-Objekt.•
publi final String getName(); liefert den Namen desThread-Objekts.
•
Das Programm legt für drei Objekte der Klasse In Threads an.•
Die Methode run() inkrementiert die Klassen-Variable x.> java In
Thread-0 read 0
Thread-0 wrote 1
Thread-1 read 1
Thread-2 read 1
Thread-1 wrote 2
Thread-2 wrote 2
Der Grund:
x Th-0
Th-1
Th-2 y
y
y 0
x Th-0
Th-1
Th-2 y
y
y 0
y = x;
x Th-0
Th-1
Th-2 y
y
y 0 0
x = y+1;
x Th-0
Th-1
Th-2 y
y
y 0 1
y = x;
x Th-0
Th-1
Th-2 y
y
y 0 1
1
y = x;
x Th-0
Th-1
Th-2 y
y
y 0 1
1
1
x = y+1;
x Th-0
Th-1
Th-2 y
y
y 0
1
1
2
x = y+1;
x Th-0
Th-1
Th-2 y
y
y 0
1
1
2
•
Inkrementieren der Variable x sollte ein atomarer Shritt sein, d.h. niht von parallel laufenden Threads unterbrohen werdenkönnen.
•
Mithilfe des Shlüsselworts synhronized kennzeihnen wir Objekt-Methoden einer Klasse L als ununterbrehbar.•
Für jedes Objekt obj der Klasse L kann zu jedem Zeitpunkt nurein Aufruf obj.synhMeth(...) einer
synhronized-Methode synhMeth() ausgeführt werden. Die
Ausführung einer solhen Methode nennt man kritishen
Abshnitt (ritial setion) für die gemeinsame Resoure obj.
•
Wollen mehrere Threads gleihzeitig in ihren kritishenAbshnitt für das Objekt obj eintreten, werden alle bis auf einen
blokiert.
ready
running
dead joining
start
yield
sleeping
sleep
// ... time over
// ... completed
join
ready
running
dead blocked
joining
start
yield
sleeping
sleep
// ... time over
// ... completed
join
•
Jedes Objekt obj mit synhronized-Methoden verfügt über:1. über ein booleshes Flag boolean loked; sowie
2. über eine Warteshlange ThreadQueue blokedThreads.
•
Vor Betreten seines kritishen Abshnitts führt ein Thread(implizit) die atomare Operation obj.lok() aus:
private void lok() {
if (!loked) loked = true; // betrete krit. Abshnitt
else { // Lok bereits vergeben
Thread t = Thread.urrentThr ead () ;
blokedThreads.enq ue ue( t);
t.state = bloked; // blokiere
}
} // end of lok()
•
Verlässt ein Thread seinen kritishen Abshnitt für obj (evt.auh mittels einer Exeption), führt er (implizit) die atomare
Operation obj.unlok() aus:
private void unlok() {
if (blokedThreads.e mpt y( ))
loked = false; // Lok frei geben
else { // Lok weiterreihen
Thread t = blokedThreads.de qu eue ();
t.state = ready;
}
} // end of unlok()
•
Dieses Konzept nennt man Monitor.x 1 false locked
count
blocked Th-1
Th-2
Th-0
x.lock();
x 1
false locked
count
blocked Th-1
Th-2
Th-0
x.inc();
x 1
locked count
blocked true
Th-1
Th-2
Th-0
x.inc();
x.lock();
x 1
locked count
blocked true
Th-1
Th-2
Th-0
x.inc();
x 1
locked count
blocked true
Th-1 Th-0
Th-2
x.unlock();
x
locked count
blocked true
Th-1
2 Th-0
Th-2
x.inc();
Th-0
x
locked count
blocked true
Th-1
2
Th-2
x.unlock();
Th-0
x
locked count
blocked true
Th-1
3
Th-2
Th-0
x
locked count
blocked Th-1
3 false
Th-2
private int ount = 0;
publi synhronized void in() {
String s = Thread.urrentThread (). get Nam e() ;
int y = ount; System.out.println( s+ " read "+y);
ount = y+1; System.out.println( s+ " wrote "+(y+1));
}
} // end of lass Count
publi lass InSyn implements Runnable {
private stati Count x = new Count();
publi void run() { x.in(); }
publi stati void main(String[℄ args) {
(new Thread(new InSynh())).start();
(new Thread(new InSynh())).start();
(new Thread(new InSynh())).start();
}
} // end of lass InSyn
> java InSyn
Thread-0 read 0
Thread-0 wrote 1
Thread-1 read 1
Thread-1 wrote 2
Thread-2 read 2
Thread-2 wrote 3
Ahtung:
•
Die Operationen lok() und unlok() erfolgen nur, wenn der Thread niht bereits vorher das Lok des Objekts erworben hat.•
Ein Thread, der das Lok eines Objekts obj besitzt, kann weitereMethoden für obj aufrufen, ohne sih selbst zu blokieren.
•
Um das zu garantieren, legt ein Thread für jedes Objekt obj, dessen Lok er niht besitzt, aber erwerben will, einen neuenZähler an:
int ountLok[obj℄ = 0;
•
Bei jedem Aufruf einer synhronized-Methode m(...) für obj wird der Zähler inkrementiert, für jedes Verlassen (auh mittelsExeptions) dekrementiert:
if (0 == ountLok[obj℄++ ) lok();
Ausführung von obj.m(...)
if (--ountLok[obj℄ == 0) unlok();
•
lok() und unlok() werden nur ausgeführt, wenn (ountLok[obj℄ == 0)Produer-Consumer-Problem
Aufgabe:
•
Zwei Threads möhten mehrere/viele Daten-Objekte austaushen.•
Der eine Thread erzeugt die Objekte einer Klasse Data(Produer).
•
Der andere konsumiert sie (Consumer).•
Zur Übergabe dient ein Puer, der eine feste Zahl N vonData-Objekten aufnehmen kann.
Producer Consumer
Producer Consumer
Producer Consumer
Producer Consumer
Producer Consumer
Producer Consumer
•
Wir denieren eine Klasse Buffer2, die (im wesentlihen) aus einem Feld der rihtigen Gröÿe, sowie zwei Verweisen intfirst, last zum Einfügen und Entfernen verfügt:
publi lass Buffer2 {
private int ap, free, first, last;
private Data[℄ a;
publi Buffer2(int n) {
free = ap = n; first = last = 0;
a = new Data[n℄;
}
...
•
Einfügen und Entnehmen sollen synhrone Operationen sein ...•
Was maht der Consumer, wenn der Produer mit derProduktion niht nahkommt, d.h. der Puer leer ist?
•
Was maht der Produer, wenn der Consumer mit derWeiterverarbeitung niht nah kommt, d.h. der Puer voll ist?
Java's Lösungsvorshlag: Warten ...
•
Was maht der Consumer, wenn der Produer mit derProduktion niht nahkommt, d.h. der Puer leer ist?
•
Was maht der Produer, wenn der Consumer mit derWeiterverarbeitung niht nah kommt, d.h. der Puer voll ist?
Java's Lösungsvorshlag: Warten ...
Producer Consumer
Producer Consumer
Producer Consumer
Producer
waiting
Consumer
Producer
waiting
Consumer
Producer
waiting
Consumer
Producer Consumer
Producer Consumer
•
Jedes Objekt (mit synhronized-Methoden) verfügt über eine weitere Shlange ThreadQueue waitingThreads am Objektwartender Threads sowie die Objekt-Methoden:
publi final void wait() throws InterruptedExept ion ;
publi final void notify();
publi final void notifyAll();
•
Diese Methoden dürfen nur für Objekte aufgerufen werden, überderen Lok der Thread verfügt !!!
•
Ausführen von wait(); setzt den Zustand des Threads aufwaiting, reiht ihn in eine geeignete Warteshlange ein, und gibt
das aktuelle Lok frei:
Thread t = Thread.urrentThr ea d() ;
t.state = waiting;
waitingThreads.en que ue (t) ;
unlok();
}
wait()
blocked
x 1
locked count
Th-1
Th-2 Th-0
waiting
true
blocked
x 1
locked count
Th-1 Th-0
waiting Th-2
true
blocked
x 1
locked count
Th-1 Th-0
waiting false
Th-2
•
Ausführen von notify(); wekt den ersten Thread in derWarteshlange auf, d.h. versetzt ihn in den Zustand bloked und
fügt ihn in blokedThreads ein:
publi void notify() {
if (!waitingThreads. is Emp ty( )) {
Thread t = waitingThreads.dequ eue () ;
t.state = bloked;
Thread t = blokedThreads.enqu eue () ;
}
}
•
notifyAll(); wekt alle wartenden Threads auf d.h. fügt alle in blokedThreads ein:publi void notifyAll() {
while (!waitingThreads. is Emp ty( )) notify();
•
Ausführen von notify(); wekt den ersten Thread in derWarteshlange auf, d.h. versetzt ihn in den Zustand bloked und
fügt ihn in blokedThreads ein:
publi void notify() {
if (!waitingThreads. is Emp ty( )) {
Thread t = waitingThreads.dequ eue () ;
t.state = bloked;
Thread t = blokedThreads.enqu eue () ;
}
}
•
notifyAll(); wekt alle wartenden Threads auf:publi void notifyAll() {
while (!waitingThreads.is Emp ty ()) notify();
}
notify()
blocked
x 1
locked count
Th-1 Th-0
waiting true
Th-2
blocked Th-2
x 1
locked count
Th-1 Th-0
waiting
true
ready
running
dead blocked
joining
start
yield
sleeping
sleep
// ... time over
// ... completed
join
ready
running
dead blocked
joining
start
yield
sleeping waiting
sleep
// ... time over
// ... completed notify
notifyAll
wait
join
...
publi synhronized void produe(Data d) throws InterruptedExeptio n {
if (free==0) wait(); free--;
a[last℄ = d;
last = (last+1)%ap;
notify();
}
publi synhronized Data onsume() throws InterruptedExept ion {
if (free==ap) wait(); free++;
Data result = a[first℄;
first = (first+1)%ap;
notify(); return result;
}
} // end of lass Buffer2
•
Ist der Puer voll, d.h. keine Zelle frei, legt sih der Produershlafen.
•
Ist der Puer leer, d.h. alle Zellen frei, legt sih der Consumershlafen.
•
Gibt es für einen Puer genau einen Produer und einenConsumer, wekt das notify() des Consumers (wenn
überhaupt, dann) stets den Produer ...
... und umgekehrt.
•
Was aber, wenn es mehrere Produers gibt? Oder mehrereConsumers ???
•
Teste nah dem Aufweken erneut, ob Zellen frei sind.•
Weke niht einen, sondern alle wartenden Threads auf ......
publi synhronized void produe(Data d)
throws InterruptedExepti on {
while (free==0) wait(); free--;
a[last℄ = d;
last = (last+1)%ap;
notifyAll();
}
...
publi synhronized Data onsume() throws InterruptedExepti on {
while (free==ap) wait();
free++;
Data result = a[first℄;
first = (first+1)%ap;
notifyAll();
return result;
}
} // end of lass Buffer2
•
Wenn ein Platz im Puer frei wird, werden sämtlihe Threadsaufgewekt obwohl evt. nur einer der Produer bzw. nur einer
der Consumer aktiv werden kann.