Kap.12: FÄDEN (Thread) DES ABLAUFS (Runnable)
Kapitel-Index
12.1 Einfache Vorrangsteuerung beim Multithreading
12.1.1 Thread-Vorrangsteuerung mit sleep(ms)
12.1.2 Thread-Vorrangsteuerung mit setPriority(prio)
12.2 Sychronisiertes (synchronized) Multithreading
12.2.1 Synchronisierter Zugriff auf gemeinsames Objekt
12.2.2 Synchronisierter Aufruf gemeinsamer Methode
12.3 Testfragen
Eine Folge von Programmschritten, einer nach dem anderen abgearbeitet, nennt man in Java 'thread' (Faden), was etwa dem Begriff
'task' in Assemblersprachen oder 'process' in anderen Programmiersprachen entspricht. Die bisherigen Kapitel behandelten nur Programme mit einer einzelnen Folge von Programmschritten, d.h.
singlethreading (single tasking, serial processes).
In diesem Kapitel wird die gleichzeitige Ausführung mehrerer
Folgen von Programmschritten behandelt, d.h. multithreading (multitasking, parallel processes). Multithreading kann intern verwirklicht werden per Hardware, d.h. Einsatz von verschiedenen
Prozessoren für threads, oder per Software, d.h. Aufteilung der
einzelnen thread-Laufzeiten in Zeitscheiben.
Dem Programmierer bietet Java im Standardpaket java.lang die
für die Implementierung des run() Laufs von threads und die
u.a. für die Vorgabe sleep(long), der nicht durch Wecken abkürzbaren Ruhezeiten der threads, das Setzen von Prioritäten
setPriority(int) und das Warten join() eines thread, z.B.
main(String[]), auf das Auslaufen eines anderen thread.
In den Beispielen dieses Kapitels werden threads vereinbart mit
Thread-Konstruktoren der Form
public Thread(Runnable target) {...}
wobei target ein Zielobjekt ist, dessen Klasse die Schnittstelle
Runnable implementieren muss (implements Runnable) mit einer Methode run(), die beim start() des threads aufgerufen wird.
12.1 Einfache Vorrangsteuerung beim Multithreading
12.1.1 Thread-Vorrangsteuerung mit sleep(ms)
Vorrangsteuerung beim multithreading ist möglich durch Angabe
von Ruhezeiten für threads mit sleep(ms) in Millisekunden ms,
z.B. (siehe WindGarantie unten)
try {for(;;){sleep(ms);out.print(wetter);}}
catch(InterruptedException e) {err.println(e.toString());}
Die Ausnahme InterruptedException, die sleep() wirft, falls ein
anderer Prozess (thread) diesen Prozess ausnahmsweise unterbricht,
muss abgefangen werden.
Im folgenden Beispiel WindGarantie schläft "wind" weniger als
"calm".
//********************** WindGarantie.java ***********************
// Garantiert Wind beim Segeln. *
// Two infinite threads in multithreading, with sleep(). *
//****************************************************************
// java.lang. Object
import static java.lang.System.*; // `System
class Garantie extends Thread // implements Runnable
{private String wetter;
private long ms;
Garantie(String wetter,long ms)
{this.wetter=wetter;this.ms=ms;}
public void run()
{try
{for(;;) {sleep(ms);out.print(wetter);}}
catch(InterruptedException e) {err.println(e.toString());}
}
}
class WindGarantie
{public static void main(String[] args) // args ungenutzt
{out.println ("Segeln Start");
String[] wetter={"calm ","wind "};
int[] ms={300,100};
Garantie[] thread=new Garantie[wetter.length];
for(int i=0;i<wetter.length;i++)
{(thread[i]= new Garantie(wetter[i],ms[i])).start();}
for(int i=0;i<wetter.length;i++)
try
{thread[i].join();} // main() waits for thread[i]
catch(InterruptedException e) {err.println(e.toString());}
out.println ("Segeln Ende"); // vorher Abbruch mit Ctrl c
}
}
//****************************************************************
Output, abgebrochen mit Ctrl(c)
----------------------------------------------------------------
Segeln Start
wind calm calm calm wind wind wind wind wind calm wind wind calm
wind calm calm wind wind wind calm wind calm wind calm wind wind
wind calm calm calm wind wind wind calm calm wind wind calm calm
12.1.2 Thread-Vorrangsteuerung mit setPriority(prio)
Vorrangsteuerung beim multithreading ist auch möglich durch Setzen von Prioritäten für threads mit setPriority(prio). Die Prioritäten sind nicht absolut zwingend, sondern nur Durchschnittswerte,
so dass auch threads minderer Priorität ihren gebührenden Anteil
am multithreading erhalten. Allerdings gibt es in Java nur 10
Prioritäten 1=Thread.MIN_PRIORITY ... Thread.MAX_PRIORITY=10 ,
darunter die mittlere Priorität Thread.NORM_PRIORITY=5, so dass
Feinsteuerung des Vorrangs schwer fällt.
Im folgenden Beispiel WindVorrang hat "wind" Vorrang vor "calm".
//*********************** WindVorrang.java ***********************
// Vorrangig Wind beim Segeln. *
// Two infinite threads in multithreading, with setPriority(). *
//****************************************************************
// java.lang. Object
import static java.lang.System .*; // ||`System
import static java.lang.Integer.MAX_VALUE; // |`Integer
import static java.lang.Thread .MIN_PRIORITY; // `Thread
class Vorrang extends Thread // implements Runnable
{private String wetter;
Vorrang(String wetter,int prio)
{this.wetter=wetter;this.setPriority(prio);}
public void forerun()
{for(int i=0;i<MAX_VALUE/300;i++){}}
public void run()
{for(;;) {forerun();out.print(wetter);}}
}
class WindVorrang
{public static void main(String[] args) // args ungenutzt
{out.println ("Segeln Start");
String[] wetter={"calm ","wind "};
int[] prio={MIN_PRIORITY,MIN_PRIORITY+1};
Vorrang[] thread=new Vorrang[wetter.length];
for(int i=0;i<wetter.length;i++)
{(thread[i]= new Vorrang(wetter[i],prio[i])).start();}
for(int i=0;i<wetter.length;i++)
try
{thread[i].join();} // main() waits for thread[i]
catch(InterruptedException e) {err.println(e.toString());}
out.println ("Segeln Ende"); // vorher Abbruch mit Ctrl c
}
}
//****************************************************************
Output, abgebrochen mit Ctrl(c)
----------------------------------------------------------------
Segeln Start
wind calm calm calm wind wind wind wind wind calm wind wind calm
wind calm calm wind wind wind calm wind calm wind calm wind wind
wind calm calm calm wind wind wind calm calm wind wind calm calm
Multithreading von threads, die nur aus der Wiederholung einer
einzelnen kurzen Druck-Anweisung bestehen, lässt sich schwer mit
Vorrang steuern. Daher wurde der Lauf run() der threads durch einen Vorlauf forerun() ergänzt, der die Laufzeit verlängert, ohne
die Druck-Anweisung zu verändern.
Die Beispiele WindGarantie (12.1.1) und WindVorrang (12.1.2) rerepräsentieren die "heile Welt" des multithreadings, denn es wird
von den threads nicht auf gemeinsame Ressourcen, z.B. eine gemeinmeinsame Variable (shared variable, 12.2), zugegriffen, ausgenommen der gemeinsame Zugriff mit out.print() auf den Ausgabe-Monitor. Aber der schnelle und kompakte Druck
in diesen Beispielen gibt dem multithreading offenbar keine Gelegenheit, durch verzahnte Ausgabe "Drucksalat" (12.2.1) oder "illegalen Zustand" (12.2.2) zu erzeugen.
12.2 Synchronisiertes (synchronized) Multithreading
In den folgenden Beispielen Tilgungen (12.2.1) und Trauungen
(12.2.2) wird von mehreren threads auf eine gemeinsame Variable
(shared variable) zugegriffen.
Für die Vermeidung von Zugriffsüberschneidungen, d.h. zwischen
dem Lesen des alten Werts der gemeinsamen Variablen und dem davon
abhängigen Schreiben eines neuen Werts durch einen thread könnte
ein anderer thread bereits den alten Wert verändert haben, bietet
Java im
Sprachumfang 'synchronized multithreading'
in Form der synchronized-Anweisung (siehe Syntax.069, 12.2.1) und
des Methoden-Modifizierers synchronized (siehe Syntax.070, 12.2.2)
sowie in der
u.a. die Methoden wait() für Versetzung von threads in den Warte-
zustand und die Methoden notify(), notifyAll() für das Wecken der
wartenden threads.
Als Alternative zum 'synchronized multithreading' gibt es seit
der Version Java 1.5 das
Paket java.util.concurrent
u.a. mit der generischen Schnittstelle BlockingQueue<E> und den
Klassen Semaphore und ThreadPoolExecutor sowie das
Paket java.util.concurrent.locks
u.a. mit der Schnittstelle Lock und der Klasse ReentrantLock, die
in diesem Buch aus Platzgründen nicht ausführlich behandelt werden
können.
Vorteile des 'synchronized multithreading' sind die Eleganz,
Flexibilität und Schlankheit des Konzepts. Vorteile der concurrent
-Pakete sind die bereitgestellten umfangreichen Schnittstellen und
Klassen, z.B. für Pool-Modelle von Threads und Semaphore oder
z.B. für Erzeuger-Verbraucher-Anwendungen, die der Benutzer sonst
selbst implementieren müsste. Java verstößt mit zwei alternativen
Konzepten für "parallele Prozesse" gegen das Prinzip des 'orthogonal design' (van Wijngaarden, ALGOL 68) für Programmiersprachen.
12.2.1 Synchronisierter Zugriff auf gemeinsames Objekt
Thread-Kommunikation in einem Block mit Zugriff auf ein gemeinsames Objekt kann mit einer synchronized-Anweisung (Synchronized
-Statement) programmiert werden, die nach Syntaxdiagramm 069 (für
Statement, hier ein Auszug 069°) von der Form ist
069: SynchronizedStatement SynchronizedAnweisung
HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
H H
H non-null H
H ReferenceType H
H
------------
-------
H
H -
synchronized -
( -
Expression
-
) -
Block
-
H
H
------------
-------
H
H locked/unlocked for others H
H H
H H
HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
z.B. (siehe unten Tilgungen)
synchronized(this) {...}
Das folgende Programm Tilgungen hat fünf threads vom Typ Schuld,
die synchronisiert zugreifen auf ein Objekt KONTO vom Typ Konto
mit einer gemeinsamen Variablen (shared variable) stand.
Multithreading von threads, die nur aus einer einzelnen kurzen
Druck-Anweisung bestehen, könnte die nichtdeterminierte Reihenfolge der Abarbeitung von threads im multithreading reduzieren auf
die determinierte Reihenfolge der thread-Vereinbarungen. Daher
wurde der Lauf run() der threads durch einen Vorlauf forerun()
ergänzt, der die Laufzeit verlängert, ohne die Druck-Anweisung zu
verändern.
//************************ Tilgungen.java ************************
// Fuenf konkurrierende Schulden werden aus einem KONTO getilgt. *
// finite threads, shared variable, synchronized block. *
// Nicht-synchronisierte Version erzeugt Fehler/Drucksalat. *
//****************************************************************
// java.lang. Object
import static java.lang.System .*; // |`System
import static java.lang.Integer.MAX_VALUE; // `Integer
class Konto
{private int stand=10000; // shared variable
public int gibStand()
{return stand;}
public void tilg(int betrag)
{
synchronized(this) // ohne diese Zeile nicht synchron!
{out.printf ("%5d EUR Schuld ",betrag);
if (betrag<=stand)
{out.println("getilgt");stand-=betrag;}
else {out.println("besteht");}
} } } // Block Freigabe
class Schuld extends Thread // implements Runnable, wrappes Konto
{private final Konto konto;
private final int betrag;
public Schuld(Konto konto,int betrag)
{this.konto=konto;this.betrag=betrag;}
public void forerun(){for(int i=0;i<MAX_VALUE/300;i++){}}
public void run() {forerun();konto.tilg(betrag);}
}
class Tilgungen
{public static void main(String[] args) // args ungenutzt
{final Konto KONTO=new Konto();
out.printf ("%5d EUR KONTO\r\n",KONTO.gibStand());
int[] betrag={1000,2000,3000,4000,5000};
Schuld[] thread=new Schuld[betrag.length];
for(int i=0;i<betrag.length;i++)
{(thread[i]=new Schuld(KONTO,betrag[i])).start();}
for(int i=0;i<betrag.length;i++)
try {thread[i].join();} // main() waits for thread[i] to die
catch(InterruptedException e) {err.println(e.toString());}
out.printf ("%5d EUR KONTO\r\n",KONTO.gibStand());
} }
//****************************************************************
Output (zufaellig)
nicht-synchron Output (zufaellig)
-----------------------
----------------------------------
10000 EUR KONTO
10000 EUR KONTO
3000 EUR Schuld getilgt
3000 EUR Schuld getilgt
5000 EUR Schuld getilgt
5000 EUR Schuld getilgt
4000 EUR Schuld besteht
4000 EUR Schuld getilgt
1000 EUR Schuld getilgt
1000 EUR Schuld getilgt
2000 EUR Schuld besteht
2000 EUR Schuld getilgt
1000 EUR KONTO
0 EUR KONTO
12.2.2 Synchronisierter Aufruf gemeinsamer Methode
Thread-Kommunikation mit Warten wait() von threads, Wecken
notifyAll() aller wartenden threads und Zugriff auf eine gemeinsame Variable (shared variable) kann mit einer als 'synchronized'
modifizierten gemeinsamen Methode programmiert werden.
//************************ Trauungen.java ************************
// Zehn konkurrierende Kandidaten werden in einem AMT getraut. *
// finite threads, shared variable, synchronized method. *
// Nicht-synchronisierte Version wirft Ausnahme "..Illegal..". *
//****************************************************************
// java.lang. Object
import static java.lang.System .*; // |`System
import static java.lang.Integer.MAX_VALUE; // `Integer
class Amt
{private boolean isBraut=false; // shared variable
synchronized // ohne diese Zeile nicht synchron!
public void trau(String name,boolean isBraut)
{while (this.isBraut==isBraut)
try {wait();} // Methode trau() Freigabe
catch(InterruptedException e) {err.println(e.toString());}
this.isBraut=isBraut;
out.print (name+(isBraut ? " & " : "\r\n"));
notifyAll (); // Wecken wartender threads
} } // Methode trau() Freigabe
class Kandidat extends Thread // implements Runnable, wrappes Amt
{private final Amt amt;
private final String name;
private final boolean isBraut;
public Kandidat(Amt amt,String name,boolean isBraut)
{this.amt=amt;this.name=name;this.isBraut=isBraut;}
public void forerun(){for(int i=0;i<MAX_VALUE/300;i++){}}
public void run() {forerun();amt.trau(name,isBraut);}
}
class Trauungen
{public static void main(String[] args) // args ungenutzt
{final Amt AMT=new Amt();
out.println ("AMT oeffnet");
String[] name={"Carla","Emily","Gerda","Henny","Paula",
"Carl" ,"Emil" ,"Gerd" ,"Hein" ,"Paul"};
boolean[] isBraut={true ,true ,true ,true ,true ,
false ,false ,false ,false ,false };
Kandidat[] thread=new Kandidat[name.length];
for(int i=0;i<name.length;i++)
{(thread[i]=new Kandidat(AMT,name[i],isBraut[i])).start();}
for(int i=0;i<name.length;i++)
try {thread[i].join();} // main() waits for thread[i] to die
catch(InterruptedException e) {err.println(e.toString());}
out.println ("AMT schliesst");
} }
//****************************************************************
Output
nicht-synchron Output
-------------
----------------------------------
AMT oeffnet
...IllegalMonitorStateException...
Carla & Hein
Paula & Gerd
Henny & Emil
Emily & Carl
Gerda & Paul
AMT schliesst
Im obigen Beispiel Trauungen verhindert die synchronisierte
Methode trau(String,boolean) im Objekt AMT der Klasse Amt
(siehe oben Trauungen)
synchronized public void trau(String name,boolean isBraut){...}
die Trauung zweier Kandidaten, die beide Bräute (isBraut==true)
oder beide keine Bräute sind, durch Vergleich mit der gemeinsamen
Variablen (shared variable) this.isBraut von Amt. Der Block der
synchronisierten Methode trau(String,boolean) entspricht dem Standard-Muster
{while ... try {wait();} catch ... notifyAll();}
und beschreibt den Zusammenhang von wait() und notifyAll(). Der
Aufruf von trau(String,boolean) durch ein aktives thread bewirkt
die Sperrung von trau für andere threads und, falls diese trau
aufrufen, deren Versetzung in den Wartezustand. Der Aufruf von
wait() bewirkt die Versetzung des aktiven threads in den Wartezustand und die sofortige Freigabe von trau für andere threads.
Es kann nur jeweils ein thread zur Zeit die gemeinsame Methode
trau(String,boolean) durchlaufen. Die anderen threads müssen in
einer Warteschlange auf die Freigabe von trau warten. Jedes thread
weiß, an welcher Stelle von trau, z.B. am Anfang oder bei wait(),
es in die Warteschlange abgestellt wurde und wird später an dieser
Stelle seinen Lauf fortsetzen.
Nach späterem Wecken und Freigabe von trau(String,boolean) für
das thread bei wait() muss für dieses thread erneut geprüft
werden, ob die Bedingung while(this.isBraut==isBraut) inzwischen
erfüllt ist und die Fortsetzung des Laufs gestattet oder ob das
thread wieder mit wait() in den Wartezustand versetzt werden muss.
Die while-Schleife kann nicht durch eine if-Anweisung ersetzt
werden, da eine if-Anweisung im Gegensatz zur while-Schleife nicht
wiederholt wird und die vielleicht inzwischen abgeänderte Bedingung nicht erneut geprüft wird.
Direkt vor Verlassen der gemeinsamen Methode trau(String,boolean)
weckt das jeweilige thread mit notifyAll() alle wartenden thread.
zu Frage
abdeckbare Antwort
---------------------------------------------
--------------------
12.1.1 Ergänze WindGarantie um einen thread
String[] Wetter=
mit "rain" und 200ms !
{"calm ","rain ",
"wind "};
int[] ms=
{300,200,100};
12.1.2 Was druckt WindVorrang aus, wenn
wind wind wind ...
int[] prio={MIN_PRIORITY,MIN_PRIORITY +1};
durch
int[] prio={MIN_PRIORITY,NORM_PRIORITY+1};
ersetzt wird?
12.2.1 Was druckt Trauungen aus, wenn
AMT oeffnet
der Methodenrumpf von forerun() durch
Carla & Carl
einen leeren Block {} ersetzt wird?
Emily & Emil
Gerda & Gerd
Henny & Hein
Paula & Paul
AMT schliesst
12.2.1 Ist while() in der Methode trau
nein,Bedingung muss
(String,boolean) ersetzbar durch if()?
mehrmals geprüft
werden
12.2.1 Ist wait() in der Methode trau
nein, notifyAll()
(String, boolean) ersetzbar durch
weckt sleep() nicht
Thread.sleep(1000) ?
12.2.2 Schützt synchronisized(this){...}
in Tilgungen vor
falschem Überschreiben von stand ?
ja
falschem Einfügen von Drucktext ?
ja,folgt aus obigem