2 Nebenläufigkeit, Threads und Synchronisation
2 Nebenläufigkeit, Threads und Synchronisation
2 Nebenläufigkeit, Threads und Synchronisation
Erfolgreiche ePaper selbst erstellen
Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.
(107)<br />
2 Nebenläufigkeit, <strong>Threads</strong> <strong>und</strong> <strong>Synchronisation</strong><br />
Gliederung:<br />
• Parallele Prozesse<br />
• Java-<strong>Threads</strong><br />
• <strong>Synchronisation</strong>, gegenseitiger Ausschluß<br />
• Das Monitorkonzept<br />
• Deadlocks<br />
2.1 Parallele Prozesse<br />
Prozess: Ausführung eines sequentiellen Programmstückes in dem zugeordneten Speicher<br />
(Adressraum). Veränderlicher Zustand: Speicherinhalt <strong>und</strong> Programmposition.<br />
Parallele Prozesse: mehrere Prozesse, die gleichzeitig auf mehreren Prozessoren ausgeführt<br />
werden.<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus<br />
(108)<br />
Verzahnte Ausführung kann parallele Ausführung simulieren. Häufiger Wechsel vermittelt den<br />
Eindruck, dass alle Prozesse gleichmäßig fortschreiten.<br />
Nebenläufige Prozesse: Prozesse, die parallel oder verzahnt ausgeführt werden können.<br />
<strong>Threads</strong> („Fäden“ der Programmausführung): Prozesse, die parallel oder verzahnt in<br />
gemeinsamem Speicher ablaufen. Die Prozessumschaltung ist besonders einfach <strong>und</strong> schnell.<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus
(109)<br />
Anwendungen paralleler Prozesse:<br />
• Benutzungsoberflächen:<br />
Die Ereignisse werden von einem speziellen Systemprozess weitergegeben. Aufwendige<br />
Berechnungen sollten nebenläufig programmiert werden, damit die Bedienung der Oberfläche<br />
nicht blockiert wird.<br />
• Simulation realer Abläufe:<br />
z. B. Produktion in einer Fabrik<br />
• Animation:<br />
Veranschaulichung von Abläufen, Algorithmen; Spiele<br />
• Steuerung von Geräten:<br />
Prozesse im Rechner überwachen <strong>und</strong> steuern externe Geräte, z. B. Montage-Roboter<br />
• Leistungssteigerung durch Parallelrechner:<br />
mehrere Prozesse bearbeiten gemeinsam die gleiche Aufgabe, z. B. paralleles Sortieren großer<br />
Datenmengen.<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus<br />
(110)<br />
2.2 Java-<strong>Threads</strong><br />
Begriff „<strong>Threads</strong>“: Prozesse, die nebenläufig, gemeinsam im Speicher des Programms ablaufen.<br />
Zwei Techniken zur Thread-Erzeugung:<br />
1. Nutzung des Interface Runnable<br />
2. Erben von der Thread-Klasse<br />
Erste Technik:<br />
Eine Benutzerklasse B implementiert das Interface Runnable<br />
verlangt das Vorhandensein einer Methode run()<br />
Erzeugung eines <strong>Threads</strong> t mit einer Instanz von B als Parameter<br />
Starten des <strong>Threads</strong> mit t.start();<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus
(111)<br />
a) Eine Benutzerklasse implementiert das Interface Runnable:<br />
class Aufgabe implements Runnable {<br />
...<br />
public void run () {<br />
// vom Interface geforderte Methode run<br />
... // das als Prozess auszuführende Programmstück<br />
}<br />
Aufgabe (...) {...}<br />
// Konstruktor<br />
}<br />
b) Der eigentliche Prozess wird als Objekt der vordefinierten Klasse Thread erzeugt:<br />
Thread auftrag = new Thread (new Aufgabe (...));<br />
c) Erst folgender Aufruf startet dann den Prozess:<br />
auftrag.start(); //Der neue Prozess beginnt, neben dem hier aktiven zu laufen.<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus<br />
Einfaches Beispiel:<br />
Einem Prozess übergibt man eine ganze Zahl n, dieser druckt n-mal diese Zahl aus <strong>und</strong><br />
hört dann auf.<br />
class T implements Runnable {<br />
private String name;<br />
private int anzahl;<br />
public void run() {<br />
System.out.print("Thread "+name+" fängt an / ");<br />
for (int i=0; i
(113)<br />
Im Hauptprogramm werden vier <strong>Threads</strong> aus den „Runnable“-Objekten vom Typ T mit<br />
verschiedenen Parametern erzeugt <strong>und</strong> gestartet:<br />
class TTest1 {<br />
public static void main (String [] args) {<br />
Thread t1 = new Thread(new T("1", 10));<br />
Thread t2 = new Thread(new T("2", 2));<br />
Thread t3 = new Thread(new T("3", 4));<br />
Thread t4 = new Thread(new T("4", 1));<br />
t1.start(); t2.start(); t3.start(); t4.start();<br />
}<br />
}<br />
Ergebnis (ein mögliches – jeder Programmlauf zeigt eine andere zufällige Reihenfolge):<br />
Thread 3 fängt an / 3333 Thread 4 fängt an / 4 Thread 1 fängt an / Thread 2<br />
fängt an / 221 / Thread 3 hört auf / Thread 4 hört auf / Thread 2 hört auf /<br />
111111111 / Thread 1 hört auf /<br />
[3 3 3 3 3 [4 4 [1 [2 2 2 1 3] 4] 2] 1 1 1 1 1 1 1 1 1 1]<br />
1: [ + + + + + + + + + + ]<br />
2: [ + + ]<br />
3: [ + + + + ]<br />
4: [ + ]<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus<br />
Zweite Technik:<br />
Eine Klasse B wird als Unterklasse der vordefinierten Klasse Thread definiert<br />
Überschreiben der Methode run()<br />
Starten einer Instanz t des <strong>Threads</strong> mit Aufruf der geerbten Methode t.start();<br />
a) Die Benutzerklasse wird als Unterklasse der vordefinierten Klasse Thread definiert:<br />
class T extends Thread {<br />
...<br />
public void run () {<br />
//überschreibt die Thread-Methode run()<br />
...<br />
} //das als Prozess auszuführende Programmstück<br />
(114)<br />
}<br />
T (...) {...}<br />
// Konstruktor<br />
b) Der Prozess wird als Objekt der Benutzerklasse erzeugt (es ist auch ein Thread-Objekt):<br />
Thread t = new T (...);<br />
c) Erst folgender Aufruf startet dann den Prozess:<br />
t.start();<br />
//der neue Prozess beginnt neben dem hier aktiven zu laufen<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus
(115)<br />
Einfaches Beispiel nochmal:<br />
class T extends Thread { ... }<br />
// Alles andere genau wie vorher!<br />
Im Hauptprogramm werden vier T-Objekte direkt als Thread eingesetzt:<br />
class TTest2 {<br />
public static void main (String [] args) {<br />
Thread t1 = new T("1", 10);<br />
Thread t2 = new T("2", 2);<br />
Thread t3 = new T("3", 4);<br />
Thread t4 = new T("4", 1);<br />
t1.start(); t2.start(); t3.start(); t4.start();<br />
}<br />
}<br />
TTest2.java<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus<br />
Gegenüberstellung<br />
(116)<br />
class T implements Runnable {<br />
...<br />
public void run() {<br />
// Prozess<br />
}<br />
...<br />
}<br />
Innerhalb der Klasse T stehen die Methoden<br />
von Thread nicht unmittelbar zur Verfügung<br />
class T extends Thread {<br />
...<br />
public void run() {<br />
// Prozess<br />
}<br />
...<br />
}<br />
Innerhalb der Klasse T stehen die Methoden<br />
von Thread zur Verfügung<br />
Thread t = new Thread(new T(...)); Thread t = new T(...);<br />
t.start();<br />
t.start();<br />
• Klasse Thread verwaltet die Ausführung der run-Methode im Zusammenspiel mit allen<br />
anderen Prozessen<br />
• Methode zum Ändern des dynamischen Zustands des Prozesses<br />
• Prioritäten, gegenseitigen Ausschluss, Signalisieren von Bedingungen, Exceptions<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus
(117)<br />
• Unterliegendes Zustandsmodell für <strong>Threads</strong>:<br />
o runnable: Prozess läuft<br />
o waiting: Prozess wartet auf einen anderen Prozess (Bedingung)<br />
o timed waiting: Prozess wartet für eine bestimmte Zeit<br />
o terminated: Prozess ist<br />
abgeschlossen<br />
new<br />
Programm startet den Thread<br />
Weitermachenl<br />
runnable<br />
Thread ist fertig<br />
terminated<br />
Warten<br />
Schlafen legen<br />
Zeitintervall<br />
abgelaufen<br />
waiting<br />
timed<br />
waiting<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus<br />
Zweites Beispiel: Eine Digitaluhr<br />
Eigenschaft: Aktualisieren der Anzeige alle 1000ms<br />
• Kurzfristig anhalten mit STOP<br />
• Weiterlaufenlassen mit GO<br />
• Ganz beenden mit dem Schliesse-Button<br />
Steuerung der Uhr mit der Variablen running:<br />
boolean running == true genau dann, wenn die Uhr "läuft"<br />
= Anzeige wechselt jede Sek<strong>und</strong>e<br />
Gr<strong>und</strong>struktur des Programms mit den zwei Klassen<br />
TDigiClock der Thread, der die Änderungen des anzuzeigenden Strings berechnet<br />
DigiClock der anzeigende JFrame, gleichzeitig Hauptklasse<br />
(118)<br />
DigiClock.java<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus
(119)<br />
Thread TDigiClock, der die Anzeige setzt<br />
class TDigiClock implements Runnable {<br />
private boolean running;<br />
DigiClock jfDigiClock;<br />
// Referenz auf den JFrame, der die Uhr anzeigt<br />
public TDigiClock (DigiClock jfDigiClock) {<br />
this.jfDigiClock = jfDigiClock; }<br />
public void run() {<br />
running = true;<br />
while (running) {<br />
jfDigiClock.setAnzeige<br />
new Date().toString().substring(11,19));<br />
try { Thread.sleep(1000); }<br />
catch (InterruptedException ex) { }<br />
}<br />
}<br />
}<br />
public void endIt() { running = false; }<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus<br />
Die Hauptklasse (Fenster, Buttons, Anzeige):<br />
class DigiClock extends JFrame implements ActionListener<br />
{ JLabel anzeige = new JLabel(" ");<br />
JButton stop = new JButton ("STOP!");<br />
JButton go = new JButton ("GO");<br />
JPanel buttons = new JPanel();<br />
// Initialisierungen im Konstruktor<br />
TDigiClock clock = new TDigiClock(this); // Anlegen eines Uhr-<strong>Threads</strong><br />
void setAnzeige(String s) { anzeige.setText(s); }<br />
// Methode zum Setzen der Anzeige, aufgerufen vom DigiClock-Thread<br />
public void actionPerformed (ActionEvent e) { // Button-Funktionen<br />
if (e.getActionCommand().equals("go")) {<br />
go.setEnabled(false);<br />
stop.setEnabled(true);<br />
new Thread(clock).start(); // Starten eines neuen <strong>Threads</strong>, der alle<br />
} // 1000ms die Anzeige setzt<br />
else {<br />
stop.setEnabled(false);<br />
go.setEnabled(true);<br />
clock.endIt();<br />
// setzt running = false, Prozess endet<br />
}<br />
}<br />
(120)<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus
DigiClock() {<br />
// Konstruktor des JFrames<br />
getContentPane().setLayout(new BorderLayout());<br />
anzeige.setHorizontalAlignment(JLabel.CENTER);<br />
anzeige.setFont(new Font("Times-Roman", Font.BOLD, 72));<br />
getContentPane().add(anzeige, BorderLayout.NORTH);<br />
buttons.setLayout(new GridLayout(1,2));<br />
stop.addActionListener(this);<br />
stop.setEnabled(true);<br />
buttons.add(stop);<br />
go.addActionListener(this);<br />
go.setEnabled(false);<br />
buttons.add(go);<br />
(121)<br />
}<br />
getContentPane().add(buttons, BorderLayout.SOUTH);<br />
...<br />
public static void main (String [] args) { // Triviales Hauptprogramm<br />
new TDigiClock();<br />
}<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus<br />
Wichtige Methoden der Klasse Thread:<br />
(122)<br />
public void run ()<br />
public void start ()<br />
public void join ()<br />
throws InterruptedException<br />
public static void sleep<br />
(long millisec)<br />
throws InterruptedException<br />
wird überschrieben mit der Methode, die die<br />
auszuführenden Anweisungen enthält<br />
startet die Ausführung des Prozesses, nur einmal<br />
pro Thread möglich!<br />
der aufrufende Prozess wartet bis der angegebene<br />
Prozess terminiert ist:<br />
try { auftrag.join(); }<br />
catch (InterruptedException e){}<br />
der aufrufende Prozess wartet mindestens die in<br />
Millisek<strong>und</strong>en angegebene Zeit:<br />
try { Thread.sleep (1000); }<br />
catch (InterruptedException e){}<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus
(123)<br />
Drittes Beispiel: Prozesse warten auf andere<br />
Variante von TTest2<br />
• Drucken von Zeichen, dazwischen aber Übergang in den Schlafmodus für eine definierte<br />
"sleep time"<br />
• Übergabe eines anderen <strong>Threads</strong>, auf den mit dem Abschluss ggf. (!) gewartet wird<br />
class T extends Thread {<br />
private String name;<br />
private int anzahl, sleepTime;<br />
private Thread wartetAuf;<br />
public void run() {<br />
System.out.println(" Thread "+name+" faengt an / ");<br />
for (int i=0; i
(125)<br />
2.3 <strong>Synchronisation</strong>, gegenseitiger Ausschluss, Monitorkonzept<br />
2.3.1 <strong>Synchronisation</strong> von Prozessen über gegenseitigen Ausschluss<br />
Wenn mehrere Prozesse die Werte gemeinsamer Variablen verändern, kann eine ungünstige<br />
Verzahnung (Parallelausführung) zu inkonsistenten Daten führen.<br />
Z. B. zwei Prozesse p <strong>und</strong> q benutzen lokale Variable tmp <strong>und</strong> eine gemeinsame Variable konto:<br />
Prozeß p tmp = konto;<br />
konto = tmp-300;<br />
Prozeß q tmp = konto; konto = tmp + 1000;<br />
p holt sich Kontostand 400 €, q auch, q schreibt 1400 €, p überschreibt mit 100 € - 1300 € futsch...!<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus<br />
• Kritische Abschnitte: zusammengesetzte Operationen, die gemeinsame Variablen lesen<br />
<strong>und</strong>/oder verändern.<br />
Prozesse müssen kritische Abschnitte unter gegenseitigem Ausschluß ("mutual exclusion")<br />
ausführen:<br />
D. h. zu jedem Zeitpunkt darf höchstens ein Prozess solch einen kritischen Abschnitt für<br />
bestimmte Variablen ausführen.<br />
(126)<br />
Andere Prozesse, die für die gleichen Variablen mit der Ausführung eines kritischen Abschnitts<br />
beginnen wollen, müssen warten.<br />
Experiment: Ist es wirklich so schlimm?<br />
Java-Programm „SynchTest“<br />
SynchTest.java<br />
• Zwei Prozesse, jeder führt 5 Buchungen durch, laufen nebenläufig ohne Kontrolle<br />
• Jeder Prozess hat eine individuelle Bearbeitungsdauer <strong>und</strong> Wartezeit zwischen je zwei<br />
Buchungen<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus
(127)<br />
class Ablauf extends Thread {<br />
private int tmp;<br />
private int betrag;<br />
private int zeitBedarf, zeitAbstand;<br />
private String name;<br />
Ablauf (String name, int zeitBedarf, int zeitAbstand, int betrag)<br />
{ ... }<br />
public void run() {<br />
for (int i=0; i
(129)<br />
2.3.2 Das Monitorkonzept<br />
• Monitor<br />
Ein Modul, der Daten <strong>und</strong> die Operationen darauf kapselt.<br />
Die kritischen Abschnitte auf den Daten werden als Monitor-Operationen formuliert.<br />
Prozesse rufen Monitor-Operationen auf, um auf die Daten zuzugreifen.<br />
Die Monitor-Operationen werden unter gegenseitigem Ausschluss ausgeführt.<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus<br />
• Monitore in Java:<br />
Anweisungsblöcke oder Methoden einer Klasse, die kritische Abschnitte auf Objektvariablen<br />
implementieren, können als synchronized gekennzeichnet werden:<br />
class Bank {<br />
synchronized public void kontoBewegung (...) {...}<br />
...<br />
private int[] konten;<br />
}<br />
Jedes Objekt der Klasse wirkt als Monitor für seine (!) Objektvariablen:<br />
Aufrufe von synchronized Methoden von mehreren Prozessen für dasselbe<br />
Objekt werden unter gegenseitigem Ausschluss ausgeführt.<br />
Zu jedem Zeitpunkt kann höchstens 1 Prozess mit synchronized Methoden auf<br />
Objektvariable zugreifen.<br />
(130)<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus
(131)<br />
Nochmal unser Experiment (erst ohne Monitor, dann mit Monitor):<br />
class Konto {<br />
private int stand = 0;<br />
// Eine Klasse für die Konten – jedes Konto ein Objekt<br />
}<br />
public void bewegung(String name, int betrag) // KRITISCHER ABSCHNITT<br />
{<br />
int tmp;<br />
tmp = stand;<br />
System.out.println(name+": Gelesener Kontostand "+ tmp);<br />
tmp = tmp + betrag;<br />
System.out.println(name+": Geschriebener Kontostand "+ tmp);<br />
stand = tmp;<br />
}<br />
• Die Ablauf-Klasse beschreibt einen Prozess, der fünfmal das Gleiche bucht<br />
• Im Hauptprogramm wird ein Konto angelegt <strong>und</strong> zwei Abläufe werden darauf losgelassen<br />
• Wieder: Übergabe von Bearbeitungszeit <strong>und</strong> Wartezeit<br />
OhneMonitorTest.java<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus<br />
(132)<br />
• Änderung: Schlüsselwort synchronized bei bewegung(...):<br />
synchronized public void bewegung(String name, int betrag) {...}<br />
• Methode kann nun von maximal einem Prozess zu einer Zeit ausgeführt werden!<br />
• Korrekter Ablauf, unabhängig von den Zeitangaben!<br />
MonitorTest.java<br />
Eigenschaft dieses Beispiels:<br />
• Sobald die "Ressource" Konto frei ist, kann jeder der beiden Ablauf-Objekte diese nutzen,<br />
es gibt keine Vorbedingung!<br />
• Daher reicht die Sicherstellung, dass kein doppelter Zugriff im kritischen Abschnitt passiert.<br />
• Das reicht aber nicht immer...<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus
(133)<br />
2.3.3 Monitore mit Bedingungssynchronisation<br />
• Bedingungssynchronisation:<br />
Ein Prozess wartet, bis eine Bedingung erfüllt ist - verursacht durch einen anderen Prozess;<br />
z. B. erst dann in einen Puffer schreiben, wenn darin Platz ist.<br />
• Bedingungssynchronisation im Monitor:<br />
Ein Prozess, der in einer kritischen Monitor-Operation auf eine Bedingung wartet, muss den<br />
Monitor freigeben, damit andere Prozesse den Zustand des Monitors ändern können.<br />
• Bedingungssynchronisation in Java:<br />
Vordefinierte Methoden der Klasse Object zur Bedingungssynchronisation:<br />
(Sie müssen aus synchronized Abschnitten heraus aufgerufen werden.)<br />
wait() blockiert den aufrufenden Prozess <strong>und</strong> gibt den Monitor frei - das<br />
Objekt, dessen synchronized Methode er gerade ausführt.<br />
notify() weckt einen der in diesem Monitor blockierten Prozesse; welcher ist<br />
nicht definiert.<br />
notifyAll() weckt alle in diesem Monitor blockierten Prozesse; sie können<br />
weiterlaufen, sobald der Monitor frei ist.<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus<br />
• Gr<strong>und</strong>struktur:<br />
a) Anforderung<br />
Solange meine Anforderung nicht erfüllt: wait(); //Hier geht’s nur weiter, wenn<br />
//andere mich "benachrichtigen"<br />
Wenn Anforderung jetzt erfüllt: Ressourcen nutzen.<br />
Sonst: wieder warten!<br />
b) Rückgabe<br />
Ressourcen zurückgeben<br />
Allen Wartenden sagen, dass sie es jetzt wieder probieren können: notifyAll();<br />
Wichtig bei a):<br />
Nachdem ein blockierter Prozess geweckt wurde, muss er die Bedingung, auf die er wartet,<br />
erneut prüfen - sie könnte schon durch schnellere Prozesse wieder ungültig sein:<br />
while (!erfüllt) try { wait(); } catch (InterruptedException e) {}<br />
(134)<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus
(135)<br />
Monitor zur Vergabe von Ressourcen („Betriebsmitteln“):<br />
Aufgabe: Eine begrenzte Anzahl gleichartiger Ressourcen wird von einem Monitor verwaltet.<br />
Prozesse fordern einige Ressourcen an <strong>und</strong> geben sie später zurück.<br />
Programmstruktur:<br />
• Eine Klasse Monitor - verwaltet die verfügbaren Ressourcen (zählen genügt)<br />
• Eine Klasse Verbraucher (Unterklasse von Thread), Verbraucherprozess - mehrfach<br />
instanziiert; alle Verbraucher „streiten“ sich um die Ressourcen<br />
• Ein Hauptprogramm: legt fest, wie viele Exemplare der Ressource es gibt, startet die<br />
Verbraucher.<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus<br />
Der Monitor:<br />
class Monitor {<br />
// Anzahl der verfügbaren Ressourcen, diese Variable<br />
private int vorhanden;<br />
// wird überwacht!<br />
Monitor(int v) { vorhanden = v; } // Initialisierung mit der Maximalzahl<br />
synchronized void anfordern(int n, int wer) { ... }<br />
// Mit dieser Anfrage an den Monitor fordert der Verbraucher “wer”<br />
// insgesamt n Elemente der Ressource an<br />
(136)<br />
}<br />
synchronized void freigeben(int n, int wer) { ... }<br />
// Mit dieser Anfrage gibt der Verbraucher “wer” die von ihm gebrauchten<br />
// insgesamt n Elemente der Ressourcen zurück<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus
Verbraucher Nummer “wer” fordert “n” Ressourcen an<br />
synchronized void anfordern(int n, int wer)<br />
(137)<br />
}<br />
// solange nicht genug verfügbar sind...<br />
while (vorhanden < n)<br />
{ // geht der Prozeß in Wartestellung <strong>und</strong> erwartet, dass man ihn “weckt”<br />
try { wait(); } catch(InterruptedException e) {}<br />
}<br />
// Jetzt ist die while-Schleife zu Ende: Es sind genug Ressourcen vorhanden!<br />
vorhanden -= n;<br />
// Her damit!<br />
// Die Rückgabe: Verbraucher “wer” gibt “n” Ressourcen zurück<br />
synchronized void freigeben(int n, int wer) {<br />
vorhanden += n;<br />
notifyAll();<br />
// Die eigentliche Rückgabe<br />
// Alle wartenden Prozesse werden informiert: Weitermachen<br />
}<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus<br />
(138)<br />
Der Verbraucherprozeß:<br />
class Verbraucher extends Thread {<br />
private Monitor mon; // Der Verbraucher merkt sich, welcher Monitor ihn<br />
bedient<br />
private int ident, // Identifikation des Prozesses (0, 1, 2, ...)<br />
wieOft, // Wie oft fordert der Prozess Ressourcen an?<br />
max; // Wie viele Ressourcen werden maximal angefordert?<br />
Verbraucher(...) {...}<br />
// Der übliche Konstruktor<br />
}<br />
public void run()<br />
{ while (wieOft > 0) // Iteration über die Anforderungen<br />
{ // Wieviel Ressourcen werden angefordert (zwischen 1 <strong>und</strong> max)<br />
int anforderung = (int)( Math.random()*(max-1) ) + 1;<br />
mon.anfordern(anforderung, ident); // Die Anforderung<br />
// Verwendung der Ressource für 1...1000 ms (Zufall)<br />
try { sleep( (int)( Math.random()*999 ) + 1); }<br />
catch(InterruptedException e) {}<br />
mon.freigeben(anforderung, ident);<br />
// Die Freigabe<br />
wieOft--;<br />
}<br />
}<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus
(139)<br />
Das Hauptprogramm:<br />
class MonitorMitRessourcen {<br />
public static void main(String [] args) {<br />
int vorhanden = 20;<br />
// Maximal sind 20 Ressourcen verfügbar<br />
Monitor mon = new Monitor (vorhanden);<br />
}<br />
// Erzeugung <strong>und</strong> starten von 5 Verbrauchern, die jeweils 4 Anforderungen erzeugen<br />
for (int i=0; i < 5; i++)<br />
new Verbraucher(mon, i, 4, vorhanden).start();<br />
// Alle Verbraucher können maximal "vorhanden" viele Ressourcen anfordern<br />
}<br />
MonitorMitRessourcen.java<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus<br />
Ein typischer Ablauf:<br />
Verbraucher 0 fordert 15 Elemente an.<br />
Verbraucher 0 bekommt 15 Elemente. Jetzt verfügbar: 5<br />
Verbraucher 1 fordert 5 Elemente an.<br />
Verbraucher 1 bekommt 5 Elemente. Jetzt verfügbar: 0<br />
Verbraucher 2 fordert 19 Elemente an.<br />
Verbraucher 3 fordert 9 Elemente an.<br />
Verbraucher 4 fordert 13 Elemente an.<br />
Verbraucher 1 gibt 5 Elemente frei. Jetzt verfügbar: 5<br />
Verbraucher 1 fordert 17 Elemente an.<br />
Verbraucher 0 gibt 15 Elemente frei. Jetzt verfügbar: 20<br />
Verbraucher 0 fordert 19 Elemente an.<br />
Verbraucher 0 bekommt 19 Elemente. Jetzt verfügbar: 1<br />
Verbraucher 0 gibt 19 Elemente frei. Jetzt verfügbar: 20<br />
Verbraucher 0 fordert 2 Elemente an.<br />
Verbraucher 0 bekommt 2 Elemente. Jetzt verfügbar: 18<br />
Verbraucher 1 bekommt 17 Elemente. Jetzt verfügbar: 1<br />
Verbraucher 0 gibt 2 Elemente frei. Jetzt verfügbar: 3<br />
Verbraucher 0 fordert 5 Elemente an.<br />
Verbraucher 1 gibt 17 Elemente frei. Jetzt verfügbar: 20<br />
Verbraucher 1 fordert 2 Elemente an.<br />
Verbraucher 1 bekommt 2 Elemente. Jetzt verfügbar: 18<br />
Verbraucher 4 bekommt 13 Elemente. Jetzt verfügbar: 5<br />
...<br />
(140)<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus
Schema: Gleichartige Ressourcen vergeben<br />
Ein Monitor verwaltet eine endliche Menge von k ≥ 1 gleichartigen Ressourcen.<br />
Prozesse fordern jeweils unterschiedlich viele (n) Ressourcen an, 1 ≤ n ≤ k, <strong>und</strong> geben sie<br />
nach Gebrauch wieder zurück.<br />
Die Wartebedingung für das Anfordern ist „Sind n Ressourcen verfügbar?“<br />
Zu jedem Zeitpunkt zwischen Aufrufen der Monitor-Methoden gilt:<br />
„Die Summe der freien <strong>und</strong> der vergebenen Ressourcen ist k.“<br />
Beispiele:<br />
• Walkman-Vergabe an Besuchergruppen im Museum<br />
• Vergabe von Parkplätzen<br />
• Taxi-Unternehmen; n = 1<br />
auch abstrakte Ressourcen, z. B.<br />
• das Recht, eine Brücke begrenzter Kapazität (Anzahl Fahrzeuge oder Gewicht) zu befahren.<br />
auch gleichartige Ressourcen mit Identität:<br />
• Der Monitor muss dann die Identitäten der Ressourcen in einer Datenstruktur verwalten.<br />
• Nummern der vom Taxi-Unternehmen vergebenen Taxis<br />
• Adressen der Speicherblöcke, die eine Speicherverwaltung vergibt<br />
(141)<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus<br />
Schema: Beschränkter Puffer<br />
Ein Monitor speichert Elemente in einem Puffer von beschränkter Größe.<br />
Produzenten-Prozesse liefern einzelne Elemente,<br />
Konsumenten-Prozesse entnehmen einzelne Elemente.<br />
Der Monitor stellt sicher, dass die Elemente in der gleichen Reihenfolge geliefert <strong>und</strong><br />
entnommen werden. (Datenstruktur: Schlange, Queue)<br />
Die Wartebedingungen<br />
lauten:<br />
• in den Puffer schreiben,<br />
nur wenn er nicht voll<br />
ist,<br />
• aus dem Puffer<br />
nehmen, nur wenn er<br />
nicht leer ist.<br />
(142)<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus
(143)<br />
Was brauchen wir?<br />
• Eine Klasse Monitor - die verwaltet die verfügbaren Ressourcen (als Individuen in einer<br />
Warteschlange)<br />
• Eine Klasse Produzent (Unterklasse von Thread) - mehrfach instanziiert; alle<br />
Produzenten wollen ihre „Produkte“ in die Schlange schreiben<br />
(Operationen enqueue - einspeichern, dequeue – entfernen)<br />
• Eine Klasse Verbraucher (Unterklasse von Thread) - mehrfach instanziiert; alle<br />
Verbraucher „streiten“ sich um die in der Schlange verwalteten Produkte<br />
• Ein Hauptprogramm: startet Produzenten <strong>und</strong> Verbraucher<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus<br />
Monitor-Klasse für beschränkte Puffer:<br />
(144)<br />
class Buffer {<br />
private Queue buf;<br />
// Schlange der Länge n zur Aufnahme der Elemente<br />
public Buffer (int n) { buf = new Queue(n); }<br />
// Die Größe n der Schlange modelliert die Begrenztheit des Puffers<br />
synchronized public void put (Object elem) { ... }<br />
// Methode zum Ablegen eines neu produzierten Elementes<br />
}<br />
synchronized public Object get () { ... }<br />
// Methode zum Abholen (Verbrauchen) eines Elementes<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus
(145)<br />
synchronized public void put (Object elem) {<br />
// ein Produzenten-Prozess versucht, ein Element zu liefern<br />
while (buf.isFull()) {<br />
// warten solange der Puffer voll ist<br />
try { wait(); } catch (InterruptedException e) {}<br />
}<br />
// Jetzt ist es soweit: Puffer ist nicht mehr voll<br />
buf.enqueue (elem);<br />
notifyAll(); // jeder blockierte Prozess prüft seine Wartebedingung<br />
synchronized public Object get () {<br />
// ein Konsumenten-Prozess versucht, ein Element zu nehmen<br />
while (buf.isEmpty())<br />
// warten solange der Puffer leer ist<br />
try { wait(); } catch (InterruptedException e) {}<br />
// Jetzt ist es soweit: Es gibt ein Element - das erste kann “geliefert” werden<br />
Object elem = buf.first();<br />
buf.dequeue();<br />
notifyAll();<br />
// jeder blockierte Prozess prüft seine Wartebedingung<br />
return elem;<br />
}<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus<br />
Die Produzenten-Klasse:<br />
(146)<br />
class Produzent extends Thread {<br />
String bezeichnung;<br />
int produktionsZahl;<br />
Buffer buf;<br />
// Konstruktor, Übernahme der Gr<strong>und</strong>daten<br />
Produzent(String ib, int wieviele, Buffer ibuf) {<br />
bezeichnung = ib; produktionsZahl = wieviele; buf = ibuf; }<br />
}<br />
public void run() {<br />
// Der Produktionsprozess<br />
while (produktionsZahl > 0) {<br />
Produkt p = new Produkt("Auto"); // Erzeugen eines neuen Autos<br />
System.out.println(bezeichnung+" hat "+p+" produziert");<br />
// Der Produzent möchte es in die Schlange einreihen<br />
buf.put(p);<br />
System.out.println(bezeichnung+" hat "+p+" abgelegt");<br />
// Ausruhen... (200 bis 700 ms)<br />
try { sleep( (int)( Math.random()*500 ) + 200 ); }<br />
catch (InterruptedException e) {}<br />
produktionsZahl--;<br />
}<br />
}<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus
(147)<br />
Die Konsumenten-Klasse<br />
class Konsument extends Thread {<br />
String bezeichnung;<br />
int verbrauchsZahl;<br />
Buffer buf;<br />
//Konstruktor, Übernahme der Gr<strong>und</strong>daten<br />
Konsument(String ib, int wieviele, Buffer ibuf) {<br />
bezeichnung = ib; verbrauchsZahl = wieviele; buf = ibuf; }<br />
// Der Konsumprozess<br />
public void run() {<br />
while (verbrauchsZahl > 0) {<br />
Produkt p = (Produkt)buf.get(); // Anforderung eines Produktes<br />
System.out.println(bezeichnung+" entnimmt "+p);<br />
// Verbrauchen (500 ... 2000 ms)<br />
try { sleep( (int)( Math.random()*1500 ) + 500 ); }<br />
catch (InterruptedException e) {}<br />
verbrauchsZahl--;<br />
}<br />
}<br />
}<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus<br />
Die Warteschlange Queue als Hilfsmittel für den Monitor:<br />
Kennt folgende Methoden<br />
• Einhängen eines neuen Objektes (ganz hinten anstellen!):<br />
void enqueue(Object o)<br />
• Entfernen des vordersten Objektes:<br />
void dequeue()<br />
• Angabe des vordersten Objektes:<br />
Object first()<br />
• Test, ob die Schlange „voll“ ist (begrenzte Kapazität!):<br />
boolean isFull()<br />
• Test, ob die Schlange leer ist:<br />
boolean isEmpty()<br />
Queue-Methoden werden angereichtert um Ausgabeanweisungen bei enqueue <strong>und</strong> dequeue<br />
(damit man sieht was passiert)<br />
(148)<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus
(149)<br />
Hilfsklasse Produkt:<br />
class Produkt {<br />
String bezeichnung;<br />
int id;<br />
// Klassenvariable zählt einfach alle Produkt-Objekte von 1...n durch<br />
static int anzahl = 0;<br />
// Das i-te Produkt heißt “Auto(i)”<br />
public String toString() { return bezeichnung+"("+id+")"; }<br />
}<br />
Produkt(String ib) { bezeichnung = ib; id = ++anzahl; }<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus<br />
Hauptprogramm:<br />
(150)<br />
class MonitorMitPuffer {<br />
public static void main(String [] args) {<br />
Buffer puffer = new Buffer(20);<br />
// Lager nimmt max. 20 Autos auf<br />
// Die Produzenten <strong>und</strong> ihre Leistungsfähigkeit: 10 VWs, 3 BMWs, ...<br />
new Produzent("VW", 10, puffer).start();<br />
new Produzent("BMW", 3, puffer).start();<br />
new Produzent("Mercedes", 2, puffer).start();<br />
new Produzent("Trabant", 18, puffer).start();<br />
}<br />
// Die Konsumenten <strong>und</strong> ihre Gier: 3 Autos für Heike, 10 für Thomek, ... :-)<br />
new Konsument("Andreas", 3, puffer).start();<br />
new Konsument("Thomek", 10, puffer).start();<br />
new Konsument("Gero", 5, puffer).start();<br />
new Konsument("Gunnar", 4, puffer).start();<br />
new Konsument("Gerd", 8, puffer).start();<br />
// Es werden 33 Autos hergestellt <strong>und</strong> 30 abgenommen<br />
}<br />
MonitorMitPuffer.java<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus
(151)<br />
Typisches Ergebnis (links: Puffergröße 20 – rechts: Puffergröße 2)<br />
VW hat Auto(1) produziert<br />
Einspeichern von Auto(1) Anzahl: 1<br />
VW hat Auto(1) abgelegt<br />
BMW hat Auto(2) produziert<br />
Einspeichern von Auto(2) Anzahl: 2<br />
BMW hat Auto(2) abgelegt<br />
Mercedes hat Auto(3) produziert<br />
Einspeichern von Auto(3) Anzahl: 3<br />
Mercedes hat Auto(3) abgelegt<br />
Trabant hat Auto(4) produziert<br />
Einspeichern von Auto(4) Anzahl: 4<br />
Trabant hat Auto(4) abgelegt<br />
Entnahme von Auto(1) Anzahl: 4<br />
Andreas entnimmt Auto(1)<br />
Entnahme von Auto(2) Anzahl: 3<br />
Thomek entnimmt Auto(2)<br />
Entnahme von Auto(3) Anzahl: 2<br />
Gero entnimmt Auto(3)<br />
......<br />
VW hat Auto(1) produziert<br />
Einspeichern von Auto(1) Anzahl: 1<br />
VW hat Auto(1) abgelegt<br />
BMW hat Auto(2) produziert<br />
Einspeichern von Auto(2) Anzahl: 2<br />
BMW hat Auto(2) abgelegt<br />
Mercedes hat Auto(3) produziert<br />
Trabant hat Auto(4) produziert<br />
Entnahme von Auto(1) Anzahl: 2<br />
Einspeichern von Auto(3) Anzahl: 2<br />
Mercedes hat Auto(3) abgelegt<br />
Andreas entnimmt Auto(1)<br />
Entnahme von Auto(2) Anzahl: 2<br />
Einspeichern von Auto(4) Anzahl: 2<br />
Trabant hat Auto(4) abgelegt<br />
....<br />
©2007 Prof. Dr. Gerd Szwillus<br />
Vorlesung „Gr<strong>und</strong>lagen der Programmierung 2“, SS 2007, Gerd Szwillus