HOCHSCHULE MUENCHEN FKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 700 – 00 – TH – 03 ----------------------------------------------------------------------------------- Programmieren in Java Kapitel 7 7. Multithreading 7.1. Erzeugung von Threads 7.2. Beenden von Threads 7.3. Synchronisation von Threads 7.4. Thread-Kommunikation über Pipes FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 711 – 00 – TH – 02 ----------------------------------------------------------------------------------- Erzeugung von Threads in Java (1) • Prinzip von Threads ◇ Unter einem Thread ("Ablauffaden") versteht man einen Kontrollfluß innerhalb eines Prozesses, in dem Anweisungen eines Programms sequentiell abgearbeitet werden. Dabei können innerhalb eines Prozesses mehrere Threads existieren, die nebenläufig (parallel bzw quasi-parallel) ablaufen Multithreading. Threads ermöglichen also eine Nebenläufigkeit innerhalb eines Programms (Prozesses) ◇ Jeder Thread besitzt einen eigenen Programmzähler und einen eigenen Stack, teilt sich aber mit den anderen Threads desselben Programms fast alle anderen Resourcen, insbesondere einen gemeinsamen Adressraum. Bezüglich der Nebenläufigkeit verhalten sich Threads zwar prinzipiell wie eigenständige Prozesse, im Unterschied zu diesen verfügen sie aber weitgehend über eine gemeinsame Ablaufumgebung. "leichtgewichtige" Prozesse. ◇ Typischerweise ist die in einem Thread abgearbeitete Anweisungsfolge in einer Funktion (Prozedur) zusammengefasst. Diese kann natürlich weitere Funktionen aufrufen, die dann ebenfalls innerhalb des Threads ausgeführt werden. ◇ Ein Thread befindet sich zu jedem Zeitpunkt in genau einem von mehreren möglichen Zuständen ◇ Ein Thread wird als aktiv (active, alive) bezeichnet, wenn seine Anweisungsfolge gestartet aber noch nicht beendet worden ist. • Threads in Java ◇ Jeder ("schwergewichtige") Prozeß, d.h. jedes in Ausführung befindliche Programm, enthält wenigstens einen Thread. In Java-Programmen wird dieser durch die Abarbeitung der main()-Funktion der Startklasse gebildet. Bei Programmen mit graphischer Oberfläche werden darüber hinaus automatisch weitere Threads erzeugt. U.a. gibt es den event-dispatching Thread, der für das Zustellen von Ereignissen und deren Bearbeitung zuständig ist. ◇ Grundsätzlich ist die Erzeugung weiterer Threads in Java sehr einfach. Das Konzept einer Nebenläufigkeit mit Threads ist direkt in die Sprache integriert worden (Sprachkomponenten zur Synchronisation) und wird durch geeignete Bibliothekskomponenten effizient unterstützt. ◇ Threads in Java besitzen eine Priorität ,einen Namen und – ab dem JDK 5.0 – eine ID (positiver long-Wert). ◇ Threads in Java sind in Thread-Gruppen zusammengefasst. Jeder Thread gehört zu einer Thread-Gruppe ◇ Threads werden in Java durch Objekte gekapselt. Prinzipiell kann ihre Gesamt-Funktionalität dabei auf zwei Objekte aufgeteilt sein : ▻ Ein Objekt definiert die vom Thread auszuführende Anweisungsfolge Es legt die durchzuführende Arbeit ("work") fest "Thread-Arbeits-Objekt", Thread-Target-Objekt. ▻ Ein Objekt dient zur Verwaltung und Steuerung (Starten, Beenden, Unterbrechen, Weiterlaufen) der Thread-Abarbeitung. Es führt die Arbeit durch ("worker") "Thread-Arbeiter-Objekt", Thread-Verwaltungs-Objekt. Die Funktionalität von beiden kann auch in einem einzigen Objekt zusammengefasst sein. ◇ Kern der Thread-Unterstützung durch die Standardbibliothek sind (enthalten im Package java.lang) : ▻ das Interface Runnable ▻ die Klasse Thread • Das Interface Runnable ◇ Alle Thread-Target-Objekte müssen Instanzen einer Klasse sein, die das Interface Runnable implementiert. ◇ Das Interface Runnable deklariert eine einzige Methode : public void run() ◇ Die Methode run() muß in einer implementierenden Klasse so überschrieben werden, dass sie die innerhalb des Threads auszuführende Anweisungsfolge definiert. ◇ Der Start eines Threads bewirkt die Abarbeitung der run()-Methode des zum Thread gehörenden Target-Objekts Der Aufruf dieser Methode erfolgt automatisch – indirekt – durch die JVM FACHHOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 712 – 01 – TH – 03 ------------------------------------------------------------------------------------ Erzeugung von Threads in Java (2-1) • Die Klasse Thread (Package java.lang) ◇ Diese Klasse definiert die Funktionalität zur Verwaltung/Steuerung von Threads. Jedes Thread-Verwaltungs-Objekt muß eine Instanz dieser Klasse oder einer von ihr abgeleiteten Klasse sein. ◇ Ein Thread-Objekt kann bei seiner Erzeugung mit einem Thread-Target-Objekt assoziiert werden. ◇ Die Klasse Thread implementiert selbst auch das Interface Runnable. Falls mit einem Thread-Objekt ein Thread-Target-Objekt assoziiert ist, ruft die in der Klasse Thread definierte run()-Methode die run()-Methode des Target-Objekts auf. Andernfalls besitzt die run()-Methode eine leere Funktionalität. In einer von Thread abgeleiteten Klasse kann die run()-Methode aber so überschrieben werden, dass sie die volle Arbeits-Funktionalität eines Threads enthält. Damit lassen sich Thread-Verwaltungs- und Thread-Arbeits-Funktionalität in einem einzigen Objekt zusammenfassen. ◇ Die Klasse Thread definiert als eingebettete Top-Level-Klasse den Aufzählungstyp Thread.State : public static enum State extends Enum<Thread.State> { ... } Die Objekte dieses Aufzählungstyps (== Aufzählungskonstante) kennzeichnen die verschiedenen Thread-Zustände. ◇ Die Klasse definiert drei statische öffentliche Datenkomponenten als Konstanten für die Prioritätsfestlegung : public static final int MIN_PRIORITY minimale Priorität, die ein Thread haben kann public static final int MAX_PRIORITY maximale Priorität, die ein Thread haben kann public static final int NORM_PRIORITY Default-Priorität, die einem Thread zugeordnet wird ◇ Konstruktoren der Klasse Thread (Auswahl) public Thread() Erzeugung eines Thread-Objekts, das mit keinem Thread-Target-Objekt assoziiert ist und einen vom System vergebenen Default-Namen besitzt public Thread(Runnable tgt) Erzeugung eines Thread-Objekts, das mit dem Thread-Target-Objekt tgt assoziiert ist und einen vom System vergebenen Default-Namen besitzt public Thread(Runnable tgt, String name) Erzeugung eines Thread-Objekts, das mit dem Thread-Target-Objekt tgt assoziiert ist und den Namen name besitzt public Thread(String name) Erzeugung eines Thread-Objekts, das mit keinem Thread-Target-Objekt assoziiert ist und den Namen name besitzt ◇ Memberfunktionen zur Thread-Steuerung (Auswahl) public void start() Start der Ausführung des Threads. Die Methode veranlasst die JVM die run()-Methode des Thread-Objekts aufzurufen public void run() Aufruf der run()-Methode eines assozierten Thread-Target-Objekts bzw leere Funktionalität, falls kein Thread-Target-Objekt assoziiert ist Muss in abgeleiteten Klassen geeignet überschreiben werden public void interrupt() Setzen des Abbruch-Flags des Threads bzw – falls der Thread in einem Wartezustand ist – Senden einer InterruptedException an den Thread public final void join() Warten auf die Beendigung des Threads public static void sleep(long ms) Übergang des aktuellen Threads in den Wartezustand für ms Millisek Kann gegebenenfalls eine InterruptedException werfen public static void yield() (temporäre) Freigabe der CPU durch den laufenden Thread FACHHOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 712 – 02 – TH – 04 ------------------------------------------------------------------------------------ Erzeugung von Threads in Java (2-2 ) • Die Klasse Thread, Forts. ◇ Memberfunktionen zur Information über den Thread-Zustand public Thread.State getState() Rückgabe des Thread-Zustands (ab dem JDK 5.0) public boolean isInterrupted() Überprüfung des Abbruch-Flags des Threads, liefert true, wenn das Abbruch-Flag gesetzt ist Das Abbruch-Flag selbst wird nicht beeinflusst public static boolean interrupted() Überprüfung des Abbruch-Flags des aktuellen Threads liefert true, wenn das Abbruch-Flag gesetzt ist Das Abbruch-Flag wird rückgesetzt public boolean isAlive() Überprüfung, ob der Thread noch aktiv ist (gestartet und noch nicht beendet) liefert true, wenn der Thread noch aktiv ist ◇ Memberfunktionen zurInformation über und Beeinflussung von Thread-Eigenschaften (Auswahl) public long getID() Rückgabe der Thread-ID (ab dem JDK 5.0) public final String getName() Rückgabe des Thread-Namens public final void setName(String name) Ändern des Thread-Namens auf name public final int getPriority() Rückgabe der Thread-Priorität public final void setPriority(int prio) Ändern der Thread-Priorität auf prio public final ThreadGroup getThreadGroup() Rückgabe einer Referenz auf das ThreadGroup-Objekt der Thread-Gruppe, zu der der Thread gehört public static int activeCount() Rückgabe der Anzahl der aktiven Threads, die sich in der Thread-Gruppe des aktuell laufenden Threads befinden public static Thread currentThread() Rückgabe einer Referenz auf das Thread-Objekt des aktuell ausgeführten Threads public String toString() Rückgabe einer String-Repräsentation des Threads. Diese enthält den Namen und die Priorität des Threads sowie den Namen seiner Thread-Gruppe ◇ Zur Synchronisation und damit zur Steuerung von Threads dienen auch einige Methoden der Klasse Object : ▻ public final void wait() public final void wait(long timeout) public final void wait(long timeout, int nanos) ▻ public final void notify() ▻ public final void notifyAll() Diese Methoden werden aber nicht als Memberfunktionen des zu steuernden Threads aufgerufen, sondern als Memberfunktion des Objekts, zu dem der Zugriff synchronisiert werden soll. Sie beeinflussen jeweils den Thread, der die Zugriffssperre (Lock) zu diesem Objekt besitzt, bzw ein oder alle Objekte, die auf die Freigabe und damit den Besitz des Locks für das Objekt warten. FACHHOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 712 – 03 – TH – 03 ------------------------------------------------------------------------------------ Erzeugung von Threads in Java (2-3 ) • Der Aufzählungstyp Thread.State (Package java.lang) ◇ static-Komponente der Klasse Thread eingebetteter Top-Level-Typ ◇ Der Typ ist abgeleitet von der parameterisierten Klasse Enum<Thread.State> ◇ Objekte dieses Typs (== Aufzählungskonstante) dienen zur Kennzeichnung der verschiedenen Thread-Zustände ◇ Die definierten Aufzählungskonstanten (und damit Thread-Zustände) sind : NEW Zustand nach Erzeugung eines Thread-Objekts vor Aufruf der start()-Methode RUNNABLE Der Thread wird gerade abgearbeitet oder ist ablaufbereit (wartet auf CPU) BLOCKED Der Thread wartet auf die Freigabe eines Monitor-Locks (zu Beginn der Ausführung einer synchronized-Anweisung/Methode bzw nach Aufwecken durch notify() auf das durch den Aufruf von wait() gewartet wurde) WAITING Der Thread wartet auf einen anderen Thread um weiterlaufen zu können (z.B. Warten auf notify() eines anderen Threads nach dem Aufruf von wait() oder Warten auf die Beendigung eines Threads für den join() aufgerufen wurde) TIMED_WAITING Der Thread wartet auf den Ablauf einer festgelegten Zeit (z.B. nach Aufruf von sleep() oder wait() mit Timeout oder join() mit Timeout) TERMINATED Der Thread ist beendet (Methode run() ist beendet) ◇ Die folgenden von der Klasse Enum<Thread.State> geerbten Methoden werden durch Thread.State überschrieben : ▻ public static final Thread.State[] values() Die Funktion gibt ein Array der definierten Aufzählungskonstanten zurück. Die Reihenfolge der Konstanten im Array entspricht ihrer Definitionsreihenfolge ▻ public static Thread.State valueOf(String name) Die Funktion gibt die Aufzählungskonstante (d.h. das entsprechende Thread.State-Objekt) mit dem Namen name zurück. Die Funktion wirft eine Exception vom Typ IllegalArgumentException, wenn name keine definierte Aufzählungskonstante bezeichnet. FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 713 – 01 – TH – 02 ----------------------------------------------------------------------------------- Erzeugung von Threads in Java (3-1 ) • Demonstrationsprogramm zu den standardmässig erzeugten Threads einfacher GUI-Programme ◇ Quellcode (Datei BasicThreadDemo.java) // BasicThreadDemo.java import java.awt.*; import java.awt.event.*; import javax.swing.*; public class BasicThreadDemo extends JFrame { Container c; JButton but; public BasicThreadDemo() { super("BasicThreadDemo"); c=getContentPane(); c.setLayout(new FlowLayout()); but = new JButton("Print Current Thread Info"); c.add(but); Thread nthr = new Thread("Leer-Thread"); System.out.print(nthr + " ID : " + nthr.getId()); System.out.println(" (State : " + nthr.getState() + ")"); ActionListener actlis = new ActionListener() { public void actionPerformed(ActionEvent e) { System.out.println("\n" + Thread.currentThread() + " threadInfo(); } }; (im Event-Listener)"); but.addActionListener(actlis); setSize(300, 180); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); System.out.println(Thread.currentThread() + " (im Konstruktor)"); } public void threadInfo() { System.out.println(); Thread[] thrarr = new Thread[Thread.activeCount()]; int threadAnz = Thread.currentThread().getThreadGroup().enumerate(thrarr); System.out.println("ThreadGroup enthaelt " + threadAnz + " aktive Threads : \n"); for (Thread thr : thrarr) { System.out.print(thr + " ID : " + thr.getId()); System.out.println(" (State : " + thr.getState() + ")"); } System.out.println(); } public static void main(String[] args) { System.out.println("Thread-Min-Priority : " + Thread.MIN_PRIORITY); System.out.println("Thread-Max-Priority : " + Thread.MAX_PRIORITY); System.out.println("Thread-Norm-Priority : " + Thread.NORM_PRIORITY); System.out.println("\n" + Thread.currentThread() + " (in main())"); new BasicThreadDemo().setVisible(true); while(true); } } FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 713 – 02 – TH – 02 ----------------------------------------------------------------------------------- Erzeugung von Threads in Java (3-2 ) • Demonstrationsprogramm zu den standardmässig erzeugten Threads einfacher GUI-Programme ◇ Programmstart und -ausgabe FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK BEREICH DATENTECHNIK V – JV – 714 – 00 – TH – 01 ----------------------------------------------------------------------------------- Erzeugung von Threads in Java (4) • Möglichkeiten zur Erzeugung von Threads in Java ◇ Es existieren zwei grundsätzliche Möglichkeiten ◇ Erste Möglichkeit : Zusammenfassung der Thread-Target- und der Thread-Verwaltungs-Funktionalität in einem einzigen Objekt. Die Klasse dieses Thread-Objekts (MyThread) muß von der Klasse Thread abgeleitet werden und die Methode run() so überschreiben, dass sie die vom Thread auszuführenden Anweisungsfolge definiert. interface Thread Runnable class MyThread extends Thread { public void run() { // ... } // ... } +run():void +start():void +run():void MyThread +run():void Der Thread wird durch Aufruf der von Thread geerbten Methode start() für ein erzeugtes MyThreadObjekt gestartet. Der Aufruf von start() bewirkt den Aufruf der überschriebenen Methode run(). new MyThread().start(); ◇ Zweite Möglichkeit : Aufteilung der Thread-Gesamt-Funktionalität auf ein Thread-Target- und ein Thread-Verwaltungs-Objekt. Die Klasse des Thread-Target-Objekts (MyRun) muß das Interface Runnable implementieren und die Methode run()mit der vom Thread auszuführenden Anweisungsfolge definieren. Das Thread-Verwaltungs-Objekt ist eine Instanz der Klasse Thread. Diesem ist bei seiner Erzeugung im Konstruktor eine Instanz der Thread-Target-Klasse MyRun zu übergeben. interface Runnable class MyRun implements Runnable { public void run() { // ... } // ... } +run():void MyRun +run():void Thread +run():void +start():void Gestartet wird der Thread durch Aufruf der Methode start() für das erzeugte Thread-Objekt. Der Aufruf dieser Methode bewirkt den Aufruf der Methode run() des gleichen Objekts. Diese wiederum ruft die run()-Methode des dem Thread-Objekt übergebenen MyRun-Objekts auf. new Thread(new MyRun()).start(); FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK BEREICH DATENTECHNIK V – JV – 715 – 00 – TH – 01 ----------------------------------------------------------------------------------- Erzeugung von Threads in Java (5) • Demonstrationsprogramm zu Threads (Thread-Klasse von Thread abgeleitet) ◇ Quellcode (Datei SimpleThreadDemo1.java) // SimpleThreadDemo1.java public class SimpleThreadDemo1 extends Thread { private String wort; private int delay; public SimpleThreadDemo1(String wrt, int del) { wort = wrt; delay = del; } public void run() { try { while(true) { System.out.print(wort + ' '); Thread.sleep(delay); } } catch(InterruptedException e) { return; } } public static void main(String[] args) { new SimpleThreadDemo1("PING", 100).start(); new SimpleThreadDemo1("pong", 33).start(); } } ◇ Programmstart und -ausgabe FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK BEREICH DATENTECHNIK V – JV – 716 – 00 – TH – 01 ----------------------------------------------------------------------------------- Erzeugung von Threads in Java (6) • Demonstrationsprogramm zu Threads (Thread-Target-Klasse implementiert Runnable) ◇ Quellcode (Datei SimpleThreadDemo1.java) // SimpleThreadDemo2.java public class SimpleThreadDemo2 implements Runnable { private String wort; private int delay; public SimpleThreadDemo2(String wrt, int del) { wort = wrt; delay = del; } public void run() { try { while(true) { System.out.print(wort + ' '); Thread.sleep(delay); } } catch(InterruptedException e) { return; } } public static void main(String[] args) { new Thread(new SimpleThreadDemo2("PING", 100)).start(); new Thread(new SimpleThreadDemo2("pong", 33)).start(); } } ◇ Programmstart und -ausgabe FACHHOCHSCHULE MUENCHEN FAKULTÄT ELEKTROTECHNIK UND INFORMATIONSTECHNIK BEREICH DATENTECHNIK V – JV – 721 – 00 – TH – 02 ----------------------------------------------------------------------------------- Beenden von Threads in Java (1) • Normales Ende eines Threads ◇ Normalerweise läuft ein Thread solange wie seine run()-Methode ausgeführt wird. Er wird erst beendet, wenn auch die letzte Anweisung dieser Methode abgearbeitet worden ist. ◇ Ein Thread lässt sich nur einmal mittels start() starten. D.h. ein einmal beendeter Thread kann nicht noch einmal ablaufen. Soll die im Thread realisierte Funktionalität nochmals ausgeführt werden, muss ein neues Thread-Objekt erzeugt und für dieses dann start() aufgerufen werden. • Vorzeitiges Beenden eines Threads ◇ Der Aufruf der Methode System.exit(...) in irgendeinem Thread bewirkt, dass die JVM und damit alle Threads beendet werden. ◇ Tritt in einemThread eine Exception auf, die nicht gefangen wird, führt das ebenfalls zum vorzeitigen Ende des Threads. ◇ Häufig läuft ein Thread in einer Schleife, die gezielt zu einem bestimmten Zeitpunkt von aussen – durch einen anderen Thread – beendet werden soll, wodurch dann i.a. auch der Thread sein Ende erreicht Ohne diese Einwirkung von aussen würde der Thread meist in einer Endlosschleife laufen. ◇ Ein derartiges gezieltes vorzeitiges Beenden einen Threads kann dadurch erreicht werden, dass der Thread an geeigneter Stelle innerhalb der Schleife ein Abbruchkriterium überprüft, dass von aussen gesetzt werden kann. Prinzipiell kann hierfür eine explizit dafür vorgesehene Datenkomponente (z.B. vom Typ boolean) eingesetzt werden. ◇ Einfacher und i.a. effizienter ist es aber, hierfür auf einen in der Klasse Thread implementierten Mechanismus zurückzugreifen. Dieser beruht auf den Memberfunktionen interrupt () und isInterrupted(). ◇ Die Methode interrupt() führt bei dem Thread-Objekt, für das sie aufgerufen wird, zum Setzen eines Abbruch-Flags, falls sich der zugehörige Thread nicht in einem Wartezustand befindet. Falls sich der Thread in einem Wartezustand befindet (z.B. ausgelöst durch den Aufruf von Thread.sleep(...)), wird an ihn eine InterruptedException geschickt, die den Wartezustand beendet. Das Abbruch-Flag wird nicht gesetzt (es wird sogar rückgesetzt, falls es gesetzt gewesen war). Um auch in diesem Fall ein Setzen des Abbruch-Flags zu erreichen, muss der zugehörige Exception-Handler seinerseits die Methode interrupt() aufrufen. Da sich der Thread nun nicht mehr in einem Wartezustand befindet, wird dadurch das Abbruch-Flag gesetzt. ◇ Mit der Methode isInterrupted() kann der Thread den Zustand seines Abbruch-Flags überprüfen. Falls die Methode true zurückliefert, war das Flag gesetzt. Dies sollte dann zum Beenden der Schleife führen. ◇ Beispiel : public void run() { while (!isInterrupted()) { try { // tue etwas Thread.sleep(500); } catch(InterruptedException e) { interrupt(); } } } FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 722 – 01 – TH – 02 ----------------------------------------------------------------------------------- Beenden von Threads in Java (2-1) • Demonstrationsprogramm zum gezielten vorzeitigen Beenden eines Threads, Teil 1 ◇ Quellcode der Klasse BlinkDemo (Datei BlinkDemo.java) // BlinkDemo.java import java.awt.*; import java.awt.event.*; import javax.swing.*; public class BlinkDemo extends JFrame { Container c; BlinkPanel blkpan; BlinkThread blkthr = null; public BlinkDemo() { super("BlinkDemo"); c=getContentPane(); blkpan = new BlinkPanel(Color.GREEN, Color.RED); c.add(blkpan); setSize(250, 200); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); blkpan.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { if (blkthr==null || !blkthr.isAlive()) { blkthr = new BlinkThread(BlinkDemo.this); blkthr.start(); } else blkthr.interrupt(); } } ); } public void blink() { blkpan.chgCol(); repaint(); } public static void main(String[] args) { BlinkDemo blkdemo = new BlinkDemo(); blkdemo.setVisible(true); } } ◇ Vom Programm erzeugtes Fenster (beide Blinkzustände) FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 722 – 02 – TH – 02 ----------------------------------------------------------------------------------- Beenden von Threads in Java (2-2) • Demonstrationsprogramm zum gezielten vorzeitigen Beenden eines Threads, Teil 2 ◇ Quellcode der Klasse BlinkPanel (Datei BlinkPanel.java) // BlinkPanel.java import java.awt.*; import javax.swing.*; public class BlinkPanel extends JPanel { Color bgc; Color vgc; boolean equcol; public BlinkPanel(Color bg, Color vg) { bgc=bg; vgc=vg; setBackground(bgc); setOpaque(true); equcol=false; } public void chgCol() { equcol=!equcol; } public void paint(Graphics g) { super.paint(g); if (equcol) setForeground(bgc); else setForeground(vgc); g.fillOval(20, 20, getWidth()-40, getHeight()-40); } } ◇ Quellcode der Klasse BlinkThread (Datei BlinkThread.java) // BlinkThread.java public class BlinkThread extends Thread { private BlinkDemo blkdem; public BlinkThread(BlinkDemo bd) { blkdem=bd; } public void run() { while (!isInterrupted()) { blkdem.blink(); try { Thread.sleep(500); } catch (InterruptedException e) {interrupt();} } } } FACHHOCHSCHULE MUENCHEN FAKULTÄT ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 731 – 00 – TH – 05 ----------------------------------------------------------------------------------- Synchronisation von Threads in Java (1) • Probleme bei Nebenläufigkeit ◇ Multithreading dient zur Realisierung einer Nebenläufigkeit innerhalb eines Prozesses. Nebenläufigkeit ermöglicht aber einen konkurierenden Zugriff zu denselben Resourcen (z.B. Daten). Dies kann zu erheblichen Problemen führen. Die beiden wichtigsten dieser Probleme sind : - Zugriffs-Wettlauf (race condition) - Verklemmung (deadlock) ◇ Zugriffs-Wettlauf (race condition) Wenn zwei oder mehr Threads konkurierend zu denselben Daten zugreifen und wenigstens einer dieser Threads die Daten verändert, - kann es zu inkonsistenten und in der Folge zu fehlerhaften Datenzuständen kommen (z.B. gleichzeitige bzw zeitlich verzahnte Durchführung je eines read-modify-write-Zyklus auf die gleichen Daten durch zwei verschiedene Threads, allgemein : Leser-Schreiber-Problem) - können von den Threads erzeugte Ergebnisse zeitabhängig sein (Ergebnisse hängen von der Zugriffsreihenfolge ab, typisch : Produzenten-Verbraucher-Problem) Zur Vermeidung dieser Probleme muss der konkurierende Zugriff synchronisiert werden. Durch eine derartige Synchronisation wird sichergestellt, dass ein "kritischer" Datenzugriff immer nur von einem Thread ausgeführt werden kann. Nur ein Thread besitzt die Zugriffsberechtigung zu den Daten (allg. : Betriebsmittel). Andere Threads, die zu den gleichen Daten (Betriebsmittel) zugreifen wollen, müssen warten, bis der erste Thread seinen Zugriff beendet hat (gegenseitiger Ausschluß, mutual exclusion). Die Sprache Java stellt hierfür Sprachmittel zu Verfügung ◇ Verklemmung (deadlock) Wenn zwei (oder mehr) Threads wechselseitig auf Daten (allgemeiner : Betriebsmittel) zugreifen wollen, die der jeweils andere Thread – infolge von Synchronisationsmassnahmen – exklusiv belegt hat, blockieren sie sich gegenseitig. Diese Situation kann z.B. auftreten, wenn Thread A die Zugriffsberechtigung zu einem Objekt X und Thread B die Zugriffsberechtigung zu einem Objekt Y besitzen und beide zu ihrer – zur Freigabe dieser Objekte führenden – Weiterarbeit den Zugriff auf das jeweils andere Objekt benötigen. Zur Lösung derartiger Probleme existieren in der Sprache Java keine Möglichkeiten. Sie müssen durch wachsame und geschickte Programmierung vermieden werden. • Synchronisation in Java : Monitore und Locks ◇ Ein Code-Bereich , in dem ein konkurierender Datenzugriff erfolgen kann, wird als kritischer Abschnitt oder kritische Region bezeichnet. Solange ein Thread einen derartigen kritischen Abschnitt durchläuft, sollte kein anderer Thread in denselben oder einen anderen kritischen Abschnitt, der sich auf dieselben Daten bezieht, eintreten. Durch Überwachung (Freigabe, Sperren) der zu bestimmten Daten (z.B. einem Objekt) gehörenden kritischen Abschnitte lässt sich der Zugriff zu diesen Daten synchronisieren. ◇ Zur Überwachung der kritischen Abschnitte werden in Java sogenannte Monitore eingesetzt. Ein Monitor stellt sicher, dass immer nur der Thread, der die Zugriffsberechtigung zu den jeweiligen Daten besitzt, den durch ihn geschützten Code ausführen kann. ◇ In Java ist mit jedem Objekt ein Monitor assoziiert, der nach entsprechender Aktivierung den Zugriff zu dem Objekt überwacht. Die Aktivierung des Monitors bezieht sich immer auf einen bestimmten überwachten Codeabschnitt. Monitore werden mittels Locks (Zugriffssperren) verwaltet. Ein Thread kann einen Monitor belegen, in dem er einen Lock auf den Monitor setzt ("lock the monitor"). Solange der Thread den Lock auf den Monitor besitzt, kann er uneingeschränkt zu dem von dem Monitor überwachten Objekt zugreifen. Versucht ein anderer Thread einen Lock auf denselben Monitor (d.h. einen Zugriff auf dasselbe Objekt) zu erlangen, muß er warten, bis der erste Thread den Lock wieder freigegeben hat ("unlock the monitor"). Erst dann kann er seinerseits den Lock setzen und danach zu dem Objekt zugreifen. Versucht ein Thread einen durch einen aktivierten Monitor überwachten Codeabschnitt auszuführen, überprüft der Monitor, ob der Thread den benötigten Lock setzen darf. Wenn ja, darf er in den Codeabschnitt eintreten, wenn nein, muß er warten bis der Lock freigegeben wird. FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK BEREICH DATENTECHNIK V – JV – 732 – 00 – TH – 01 ----------------------------------------------------------------------------------- Synchronisation von Threads in Java (2) • Demonstrationsprogramm zum Zugriffs-Wettlauf bei Multithreading ◇ Quellcode der Klasse LValBox (Datei LValBox.java) public class LValBox { private long val; public LValBox(long lv) { val=lv; } public long getVal() { return val; } public void setVal(long lv) { val=lv; } } ◇ Quellcode der Klasse LVBChangeThread (Datei LVBChangeThread.java) // LVBChangeThread.java public class LVBChangeThread extends Thread { private LValBox vbox; public LVBChangeThread(LValBox vb) { vbox=vb; } public void run() { long val = 0; for (int i=0; i<1000; i++) { val=vbox.getVal(); val++; int s=0; while (s<200000) s+=1; vbox.setVal(val); } } // nur zum Zeitverbrauch public static void main(String[] args) { LValBox vbox = new LValBox(0); LVBChangeThread t1 = new LVBChangeThread(vbox); LVBChangeThread t2 = new LVBChangeThread(vbox); t1.start(); t2.start(); while (t1.isAlive() || t2.isAlive()); System.out.println("letzter Wert : " + vbox.getVal()); } } ◇ Beispiel für Start und Ausgabe des Programms FACHHOCHSCHULE MUENCHEN FAKULTÄT ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 733 – 00 – TH – 05 ----------------------------------------------------------------------------------- Synchronisation von Threads in Java (3) • Die synchronized-Anweisung ◇ Diese Anweisung dient zum Aktivieren des Monitors, mit dem der Zugriff zu einem Objekt (Ziel-Objekt) synchronisiert werden kann. Sie legt das betreffende Ziel-Objekt fest und definiert die der Überwachung unterliegende Anweisungsfolge ◇ Syntax : synchronized ( Ausdruck ) { Anweisung } Der in runden Klammern stehende Ausdruck muß als Wert eine Referenz auf ein Objekt ergeben. Er bestimmt das Objekt, zu dem der Zugriff synchronisiert werden soll (Ziel-Objekt). Die in geschweiften Klammern stehende Anweisungsfolge (der "Rumpf" derAnweisung) bildet den überwachten kritischen Codeabschnitt, in dem i.a. (aber nicht immer) zu dem Zielobjekt zugegriffen werden sollte. ◇ Beispiel : synchronized (vbox) { val=vbox.getVal(); val++; vbox.setVal(val); } ◇ Wirkung : ▻ Die synchronized-Anweisung überprüft, ob der aufrufende Thread den Lock auf den Monitor des durch den Ausdruck referierten Objekts, das Ziel-Objekt, (im Beispiel : vbox) setzen kann (oder bereits besitzt). ▻ Wenn ja, wird der Lock durch den Thread gesetzt und die im Rumpf stehende Anweisungsfolge ausgeführt. Nach Beendigung der Anweisungsfolge (entweder normal oder durch das Werfen einer nicht gefangenen Exception) wird der Lock wieder freigegeben. ▻ Wenn nein, besitzt ein anderer Thread bereits den Lock. In dem Fall muß der aufrufende Thread warten, bis der Lock von dem besitzenden Thread wieder frei gegeben wird. Erst dann kann die Anweisung wie oben skizziert weiter ausgeführt werden. ◇ Weitere Anmerkungen ▻ Die synchronized-Anweisung bewirkt, dass das Setzen und Freigeben eines Locks immer als Paar auftritt. Dadurch ist es nicht möglich, dass ein von einem Thread gehaltener Lock von diesem nicht wieder freigegeben wird (ausser im Fall einer Verklemmung) ▻ Der Zugriff eines Threads zu einem Objekt, auf dessen Monitor ein anderer Thread den Lock besitzt, ohne Aktivierung der Monitor-Überwachung (d.h. ohne entsprechende synchronized-Anweisung) ist grundsätzlich möglich. In diesem Fall findet keine Synchronisation statt. ▻ Die synchronized-Anweisung erlaubt die Festlegung eines beliebigen Objekts als Ziel-Objekt. Wird sie in dem Code, der das Ziel-Objekt verwendet, eingesetzt, spricht man von client-seitiger Synchronisation. Damit die Synchronisation tatsächlich funktionieren kann, muß hierbei jeder das Objekt benutzende Client einen synchronisierten Zugriff sicherstellen. Sie kann aber auch in den Memberfunktionen des Ziel-Objekts selbst eingesetzt werden. In diesem Fall muss das Ziel-Objekt durch this referiert werden. Das Ziel-Objekt sorgt selbst für seine Zugriffs-Synchronisation. Eine unsynchronisierte Verwendung durch Clients ist nicht möglich. Manchmal wird dies als server-seitige Synchronisation bezeichnet. ▻ Da Synchronisation i.a. zu einem Performenz-Verlust führen kann (Threads müssen auf Zugriff warten), sollte ein Monitor-Lock immer nur so kurz wie möglich besessen werden, d.h. der überwachte Codeabschnitt sollte auf die unbedingt notwendigen Anweisungen beschränkt werden. FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK BEREICH DATENTECHNIK V – JV – 734 – 00 – TH – 01 ----------------------------------------------------------------------------------- Synchronisation von Threads in Java (4) • Demonstrationsprogramm zur synchronized-Anweisung (client-seitige Synchronisation) ◇ Quellcode der Klasse LValBox (Datei LValBox.java) public class LValBox { private long val; public LValBox(long lv) { val=lv; } public long getVal() { return val; } public void setVal(long lv) { val=lv; } } ◇ Quellcode der Klasse LVBChangeThreadSync (Datei LVBChangeThreadSync.java) // LVBChangeThreadSync.java public class LVBChangeThreadSync extends Thread { private LValBox vbox; public LVBChangeThreadSync(LValBox vb) { vbox=vb; } public void run() { long val = 0; for (int i=0; i<1000; i++) { synchronized (vbox) { val=vbox.getVal(); val++; int s=0; while (s<200000) s+=1; // nur zum Zeitverbrauch vbox.setVal(val); } } } public static void main(String[] args) { LValBox vbox = new LValBox(0); LVBChangeThreadSync t1 = new LVBChangeThreadSync(vbox); LVBChangeThreadSync t2 = new LVBChangeThreadSync(vbox); t1.start(); t2.start(); while (t1.isAlive() || t2.isAlive()); System.out.println("letzter Wert : " + vbox.getVal()); } } ◇ Beispiel für Start und Ausgabe des Programms FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 735 – 00 – TH – 02 ----------------------------------------------------------------------------------- Synchronisation von Threads in Java (5) • synchronized-Methoden ◇ Sowohl statische als auch nichtstatische Memberfunktionen können mit dem Methoden-Modifizierer synchronized definiert werden. ◇ Hierdurch wird ein Monitor für die gesamte Memberfunktion aktiviert. Die gesamte Memberfunktion unterliegt damit einer Synchronisations-Überwachung. ◇ Bei nichtstatischen Memberfunktionen ist das Ziel-Objekt, für das der Monitor aktiviert wird, das jeweils aktuelle Objekt, für das die Memberfunktion aufgerufen wird. Damit stellt die Modifizierung einer nichtstatischen Memberfunktion mit synchronized eine abkürzende Schreibweise für eine "normale" Memberfunktion dar, bei der der gesamte Funktionsrumpf in eine synchronized-Anweisung mit der Ziel-Objekt-Referenz this eingeschlossen ist. Die beiden folgenden Formulierungen sind in ihrer Wirkung identisch : public synchronized void myFunc() { // tue etwas } public void myFunc() { synchronized (this) { // tue etwas } } ◇ Bei statischen Memberfunktionen ist das Ziel-Objekt das Class-Objekt, das mit der Klasse der Memberfunktion assoziiert ist. Auch hier gilt, dass die Modifizierung der Memberfunktion mit synchronized eine abkürzende Schreibweise darstellt für eine nicht-modifizierte Memberfunktion, bei der der gesamte Funktionsrumpf in eine synchronized-Anweisung gekapselt ist. Wenn der Name der Klasse, zu der die statische Memberfunktion gehört, MyClass ist, lautet die Referenz auf das Ziel-Objekt hier MyClass.class (ein derartiger Ausdruck wird class literal genannt) Statische synchronized-Methoden können lediglich den Zugriff zu statischen Datenkomponenten der Klasse synchronisieren. ◇ Der durch den Aufruf einer statischen synchronized-Methode erlangte Besitz eines Monitor-Locks für das Class-Objekt einer Klasse hat keinen Einfluß auf den Zugriff zu irgendwelchen Objekten dieser Klasse. Ein Thread kann also durchaus nichtstatische synchronized-Methoden für ein Objekt einer Klasse aufrufen, während ein anderer Thread einen durch eine statische Methode erworbenen Lock für das Class-Objekt dieser Klasse besitzt. ◇ Die Definition von synchronized-Methoden implementiert immer eine serverseitige Synchronisation. Clients können also das Einrichten von Monitoren nicht "vergessen". Andererseits unterliegen hierbei immer vollständige Methoden der Synchronisations-Überwachung. Gegebenenfalls kann es effizienter sein, nur für die wirklich notwendigen Teile einer Methode eine entsprechende Überwachung vorzusehen. Hierfür müssen dann entsprechende synchronized-Anweisungen eingesetzt werden. Ausserdem ist serverseitige Synchronisation nicht immer möglich. So lassen sich read-modify-write-Zugriffe durch einen Client i.a. nicht als Memberfunktion des Server-Objekts formulieren (Server-Objekt kennt Art des modify nicht) • Wirksamkeit der Synchronisation mittels synchronized-Anweisungen und -Methoden ◇ Die durch synchronized-Anweisungen und –Methoden implementierte Synchronisation löst direkt nur das Problem der inkonsistenten und fehlerhaften Daten bei nebenläufigem Lesen und Schreiben (Leser-Schreiber-Problem) (Sperrsynchronisation, gegenseitiger Ausschluß, mutual exclusion) ◇ Das zu zeitabhängigen Ergebnissen führende Problem der gegenseitigen Datenabhängigkeit von Threads (ProduzentenVerbraucher-Problem, Reihenfolge des Zugriffs ist wichtig) lässt sich damit aber nicht ausreichend lösen. Hierfür muß die Sperrsynchronisation um zusätzliche Mechanismen ergänzt werden ( Zustands- bzw Ereignis-Synchronisation). Derartige Mechanismen werden z.B. durch Synchronisations-Methoden der Klasse Object zur Verfügung gestellt FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 736 – 00 – TH – 02 ----------------------------------------------------------------------------------- Synchronisation von Threads in Java (6) • Die Methoden wait() und notify() der Klasse Object ◇ Neben einem Monitor ist jedem Objekt in Java auch eine Warteliste (wait set) zugeordnet. In diese Warteliste werden Threads eingetragen, die vom Scheduler unterbrochen worden sind und zu ihrer Fortsetzung auf den Eintritt eines Ereignisses warten. Nach der Erzeugung eines Objekts ist diese Warteliste leer. ◇ Die in der Klasse Object definierten – nicht überschreibbaren – Methoden wait() und notify() interagieren mit dem Scheduler und arbeiten mit der Warteliste des Objekts, für das sie jeweils aufgerufen werden. Sie stellen einen weiteren Mechanismus zur Synchronisation von Threads zur Verfügung, der das Monitor-Konzept ergänzt. ◇ Sowohl wait() als auch notify() dürfen nur für ein Objekt aufgerufen werden, dessen Monitor-Lock von dem jeweiligen aktuellen Thread besessen wird, d.h. sie dürfen nur innerhalb von synchronized-Code verwendet werden. ◇ Ein Aufruf von wait() bewirkt, dass der aktuelle Thread angehalten, der von ihm besessene Lock auf den Monitor des Ziel-Objekts freigegeben und der Thread in die Thread-Warteliste des Ziel-Objekts eingetragen wird. Für den Scheduler wird dieser Thread als – auf ein Ereignis – wartend markiert. Ein anderer – eventuell schon darauf wartender – Thread kann damit den Lock auf den Monitor des Objekts bekommen. ◇ Ein Aufruf von notify() kennzeichnet den Eintritt des Ereignisses, auf das die in der Warteliste eingetragenen Threads warten. Er bewirkt, dass einer dieser Threads (oder auch alle) aufgeweckt, d.h. als ablaufbereit markiert wird. Damit kann dieser dann vom Scheduler wieder für die CPU-Zuteilung berücksichtigt werden. Wird er als nächster laufender Thread ausgewählt, bekommt er den Lock auf den Monitor des Objekts wieder zurück. Allerdings ist das erst dann möglich, wenn der Thread, der notify() aufgerufen hat, den Lock auf den Monitor des Objekts seinerseits wieder freigegeben hat. Der Weiterlauf des wartenden Threads führt zur Beendigung der von ihm aufgerufenen wait()-Methode. ◇ Es gibt Varianten der wait()-Methode, mit der der aufrufende Thread ein Timeout-Interval festlegen kann, nachdem er spätestens aufgeweckt werden möchte, auch dann, wenn bis dahin kein notify()-Aufruf für das Objekt erfolgt ist. ◇ wait()-Methoden public final void wait() Veranlasst, dass der aktuelle Thread in den Wartezustand übergeht bis ein anderer Thread notify() oder notifyAll() für dasselbe Ziel-Objekt aufruft. Der aktuelle Thread muss den Lock für dieses Objekt besitzen. Der Lock wird für die Dauer des Wartezustands abgegeben public final void wait(long timeout) wie wait(), jedoch wird der Wartezustand spätestens nach Ablauf von timeout Millisek verlassen public final void wait(long timeout, int nanos) wie wait(), jedoch wird der Wartezustand spätestens nach Ablauf von (timeout Millisek + nanos Nanosek) verlassen Jede der wait()-Methoden kann eine InterruptedException werfen, die gefangen werden muß. Diese Exception wird geworfen, wenn ein anderer Thread für den wartenden Thread interrupt() aufruft ◇ notify()-Methoden public final void notify() Weckt einen der Threads aus der Thread-Warteliste des Ziel-Objekts auf Welcher Thread aufgeweckt wird, kann nicht vorausgesagt werden public final void notifyAll() Weckt alle Threads aus der Thread-Warteliste des Ziel-Objekts auf FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 737 – 00 – TH – 03 ----------------------------------------------------------------------------------- Synchronisation von Threads in Java (7) • Ereignissynchronisation mittels wait() und notify() ◇ Unter Ereignis- oder Zustands-Synchronisation versteht man die Sicherstellung einer definierten zeitlichen Reihenfolge bei der Abarbeitung von mehreren Threads. Die Reihenfolge wird durch den Eintritt bestimmter Ereignisse (bzw bestimmter Zustände) gesteuert. Ein wartender Thread muss von einem anderen Thread über den Eintritt eines erwarteten Ereignisses informiert werden Zwischen Threads muß eine elementare Kommunikation stattfinden. ◇ Eine definierte Abarbeitungs-Reihenfolge von Threads ist immer dann notwendig, wenn diese voneinander datenabhängig sind. Ein typisches Beispiel hierfür ist die Erzeugung von Daten durch einen Thread und deren Verwendung durch einen anderen Thread. Erzeuger-Verbraucher-Problem. ◇ Die Object-Methoden wait() und notify() ermöglichen die für eine Ereignissynchronisation notwendige elementare Thread-Kommunikation. Ein Thread wartet bis ein bestimmtes Ereignis eintritt, das eine von ihm zu überprüfende Bedingung beeinflusst haben könnte. Mittels notify() wird er von einem anderen Thread über den Eintritt dieses Ereignisse informiert. ◇ Der Code zur Realisierung einer Ereignissynchronisation kann sowohl in dem Objekt, auf das der Monitor-Lock bestehen muß (serverseitige Sync.) als auch in einem anderen Objekt (z.B. im Thread-Objekt, clientseitige Sync.) enthalten sein. ◇ Typische prinzipielle Formulierung des Synchronisations-Codes : ▻ im wartenden (empfangenden) Thread (hier in einer Methode des Objekts auf das der Thread den Lock besitzt, serverseitige Synchronisation) synchronized void doSomething() { while (!bedingung) wait(); // Weiterarbeit } Anmerkung : Die Überprüfung der Bedingung, die über Warten oder Weiterarbeit entscheidet, sollte unbedingt in einer Schleifenanweisung und nicht in einer if-Anweisung erfolgen. Nur dann kann der Thread nach Beendigung der wait()-Anweisung auch überprüfen, ob die erwartete Bedingung jetzt tatsächlich erfüllt ist. Sollte dies nicht der Fall sein, geht der Thread mit einem erneuten wait()-Aufruf sofort wieder in den Wartezustand über. ▻ im aufweckenden (sendenden) Thread (hier in einer Methode des Objekts auf das der Thread den Lock besitzt, serverseitige Synchronisation) synchronized void changeCondition() { // tue etwas, das bedingung veraendert notifyAll() // oder : notify() } • Unterbrechung von Threads mittels wait() und notify() ◇ Die Methoden wait() und notify() können auch zur Unterbrechungssteuerung eines typischerweise in einer Schleife laufenden Threads eingesetzt werden. ◇ Dabei legt der Wert einer logischen Variablen fest, ob der gesteuerte Thread unterbrochen werden oder weiterlaufen soll. Der gesteuerte Thread überprüft bei jedem Schleifendurchlauf diese Variable. Hat sie den Wert für "unterbrechen" legt sich der Thread durch den Aufruf von wait() "schlafen". Der steuernde Thread ändert den Wert dieser Variablen und informiert den zu steuernden Thread mittels notify() wenn sie den Wert für "weiterlaufen" angenommen hat. Dadurch wird dieser wieder aufgeweckt und kann weiterlaufen. FACHHOCHSCHULE MUENCHEN FAKULTÄT ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 738 – 01 – TH – 03 ----------------------------------------------------------------------------------- Synchronisation von Threads in Java (8-1) • Demonstrationsprogramm zum Erzeuger-Verbraucher-Problem ◇ Ein Erzeuger-Thread erzeugt laufend einen zwischen 0 und 1000 liegenden int-Wert und legt diesen in einem Buffer ab. Ein Verbraucher-Thread soll den im Buffer befindlichen int-Wert jeweils wieder auslesen. ◇ Quellcode des Interfaces IntBuffer (Datei IntBuffer.java) public interface IntBuffer { public void put(int val); public int get(); public int getBuffSize(); } ◇ Quellcode der Klasse IntErzeuger (Datei IntErzeuger.java) public class IntErzeuger extends Thread { private IntBuffer buff; private int cnt; // Anz. der zu erzeugenden Bufferfuellungen public IntErzeuger(IntBuffer b, int anz) { buff = b; cnt = anz; } public void run() { int val; int wdh=cnt*buff.getBuffSize(); for (int i=0; i<wdh || cnt==0; i++) { val = (int)(Math.random()*1000); buff.put(val); System.out.println("erzeugt : " + val); try { Thread.sleep((int)(Math.random()*200)); } catch(InterruptedException e) {} } } } ◇ Quellcode der Klasse IntVerbraucher (Datei IntVerbraucher.java) public class IntVerbraucher extends Thread { private IntBuffer buff; private int cnt; // Anz. der zu verbrauchenden Bufferfuellungen public IntVerbraucher(IntBuffer b, int anz) { buff = b; cnt = anz; } public void run() { int val; int wdh=cnt*buff.getBuffSize(); for (int i=0; i<wdh || cnt==0 ; i++) { val = buff.get(); System.out.println(" verbraucht : " + val); try { Thread.sleep((int)(Math.random()*200)); } catch(InterruptedException e) {} } } } FACHHOCHSCHULE MUENCHEN FAKULTÄT ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 738 – 02 – TH – 03 ----------------------------------------------------------------------------------- Synchronisation von Threads in Java (8-2) • Demonstrationsprogramm zum Erzeuger-Verbraucher-Problem, Forts. ◇ Ohne Ereignissynchronisation : Quellcode der Klasse UnsyncIntBuffer (Datei UnsyncIntBuffer.java) // UnsyncIntBuffer.java public class UnsyncIntBuffer implements IntBuffer { private int buff; public synchronized void put(int val) { buff= val; } public synchronized int get() { int val = buff; return val; } public int getBuffSize() { return 1; } public static void main(String[] args) { IntBuffer ibuff = new UnsyncIntBuffer(); new IntErzeuger(ibuff, 8).start(); new IntVerbraucher(ibuff, 8).start(); } } ◇ Beispiel für Start und Ausgabe des Programms FACHHOCHSCHULE MUENCHEN FAKULTÄT ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 738 – 03 – TH – 03 ----------------------------------------------------------------------------------- Synchronisation von Threads in Java (8-3) • Demonstrationsprogramm zum Erzeuger-Verbraucher-Problem, Forts. ◇ Mit Ereignissynchronisation : Quellcode der Klasse SyncIntBuffer (Datei SyncIntBuffer.java) public class SyncIntBuffer implements IntBuffer { private int buff; private boolean voll; public SyncIntBuffer() { voll = false; } public synchronized void put(int val) { while (voll) { try { wait(); } catch (InterruptedException e) { } } buff = val; voll = true; notifyAll(); // Mitteilung, dass Buffer voll ist } public synchronized int get() { while (!voll) { try { wait(); } catch (InterruptedException e) { } } int val = buff; voll = false; notifyAll(); // Mitteilung, dass Buffer leer ist return val; } public int getBuffSize() { return 1; } public static void main(String[] args) { IntBuffer ibuff = new SyncIntBuffer(); new IntErzeuger(ibuff, 8).start(); new IntVerbraucher(ibuff, 8).start(); } } ◇ Beispiel für Start und Ausgabe des Programms FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK BEREICH DATENTECHNIK V – JV – 739 – 01 – TH – 01 ----------------------------------------------------------------------------------- Synchronisation von Threads in Java (9-1) • Demonstrationsprogramm zum Unterbrechen/Weiterlaufen von Threads, Teil 1 ◇ Quellcode der Klasse BlinkDemo2 (Datei BlinkDemo2.java) import java.awt.*; import java.awt.event.*; import javax.swing.*; public class BlinkDemo2 extends JFrame { Container c; BlinkPanel blkpan; BlinkThread2 blkthr = null; public BlinkDemo2() { super("BlinkDemo2"); c=getContentPane(); blkpan = new BlinkPanel(Color.YELLOW, Color.BLUE); c.add(blkpan); setSize(250, 170); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); blkpan.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { System.out.println(Thread.currentThread()); if (blkthr==null) { blkthr = new BlinkThread2(BlinkDemo2.this); blkthr.start(); } else synchronized(blkthr) { blkthr.toggleBreak(); blkthr.notify(); } } } ); } public void blink() { blkpan.chgCol(); repaint(); } public static void main(String[] args) { BlinkDemo2 blkdemo = new BlinkDemo2(); blkdemo.setVisible(true); } } ◇ Vom Programm erzeugtes Fenster (beide Blinkzustände) FACHHOCHSCHULE MUENCHEN FAKULTÄT ELEKTROTECHNIK UND INFORMATIONSTECHNIK BEREICH DATENTECHNIK V – JV – 739 – 02 – TH – 01 ----------------------------------------------------------------------------------- Synchronisation von Threads in Java (9-2) • Demonstrationsprogramm zum Unterbrechen/Weiterlaufen von Threads, Teil2 ◇ Quellcode der Klasse BlinkPanel (Datei BlinkPanel.java) s. Demonstrationsprogramm zum gezielten vorzeitigen Beenden eines Threads (V-JV-722-02) ◇ Quellcode der Klasse BlinkThread2 (Datei BlinkThread2.java) // BlinkThread2.java public class BlinkThread2 extends Thread { private BlinkDemo2 blkdem; private boolean unterbrechung; public BlinkThread2(BlinkDemo2 bd) { blkdem=bd; unterbrechung=false; } public void toggleBreak() { unterbrechung = !unterbrechung; } public void run() { System.out.println(Thread.currentThread() + " Begin"); System.out.println("Anz. aktiver Threads : " + Thread.activeCount()); while (true) { try { synchronized (this) { while(unterbrechung) wait(); } blkdem.blink(); Thread.sleep(500); } catch (InterruptedException e) { break;} } System.out.println(Thread.currentThread() + " Ende"); } } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 741 – 00 – TH – 03 ----------------------------------------------------------------------------------- Thread-Kommunikation über Pipes in Java (1) • Pipes ◇ Eine Pipe ist ein unidirektionaler Kommunikationskanal, über den zwei Prozesse bzw Threads gekoppelt werden können : Eine Pipe besitzt eine Schreibseite und eine Leseseite. Ein Prozess/Thread ist mit der Schreibseite verbunden und schreibt Daten in die Pipe, der andere Prozess/Thread ist mit der Leseseite verbunden und liest die geschriebenen Daten in genau der gleichen Reihenfolge wieder aus. ◇ Pipes sind spezielle Streams. Sie werden also dateiähnlich verwendet, mit der Besonderheit, dass zur Schreibseite einer Pipe nur schreibend und zur Leseseite nur lesend zugegriffen werden kann. ◇ Pipes werden mittels eines im Arbeitsspeicher angelegten Puffers implementiert. Wenn der Puffer voll ist, blockiert der schreibende Prozess/Thread , wenn der Puffer leer ist blockiert der lesende Prozess/Thread. Pipes stellen einen Synchronisationsmechanismus zur Verfügung, mit dem sich das Erzeuger-/VerbraucherProblem sehr effizient lösen lässt. • Pipes in Java ◇ In Java lassen sich Pipes sowohl als Byte-Streams als auch als Zeichen-Streams realisieren. Hierfür existieren – im Package java.io – die Klassen ▻ PipedOutputStream (abgeleitet von OutputStream) und PipedInputStream (abgeleitet von InputStream) (Byte-Streams), sowie ▻ PipedWriter (abgeleitet von Writer) und PipedReader (abgeleitet von Reader) (Zeichen-Streams). ◇ Objekte der Klasse PipedOutputStream bilden die Schreibseite, Objekte der Klasse PipedInputStream bilden die Leseseite einer Pipe. Sie werden immer paarweise und immer in verschiedenen Threads eingesetzt. Analoges gilt für Objekte der Klassen PipedWriter und PipedReader. ◇ Klassendiagramme Byte-Stream-Pipe OutputStream PipedOutputStream 1 1 Zeichen-Stream-Pipe InputStream Writer PipedInputStream PipedWriter Reader 1 1 PipedReader ◇ Die Kopplung zwischen je einem PipedOutputStream- und einem PipedInputStream-Objekt (bzw einem PipedWriter- und einem PipedReader-Objekt) kann gleich bei der Objekterzeugung (Konstruktor-Parameter) oder später mittels einer geeigneten Memberfunktion erfolgen. Dabei ist es gleichgültig, ob ein PipedInputStream-Objekt an ein PipedOutputStream-Objekt (bzw ein PipedReader-Objekt an ein PipedWriter-Objekt) gekoppelt wird oder umgekehrt. Wenn bereits eine Kopplung besteht, führt der Versuch einer erneuten Kopplung zum Werfen einer IOException. ◇ Der für eine Pipe verwendete Puffer umfasst 1024 Bytes. Diese Größe kann nicht verändert werden. ◇ Der Versuch der Verwendung (Schreiben bzw Lesen) eines Pipe-Stream-Objekts, das nicht an eine Gegenseite gekoppelt ist, führt zum Werfen einer IOException. Dies ist z.B. auch dann der Fall, wenn der Thread der Gegenseite zwischenzeitlich beendet worden ist. FACHHOCHSCHULE MUENCHEN FAKULTÄT ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 742 – 00 – TH – 03 ----------------------------------------------------------------------------------- Thread-Kommunikation über Pipes in Java (2) • Die Klasse PipedOutputStream (Package java.io) ◇ Objekte dieser Klasse bilden die Schreibseite einer Byte-Stream-Pipe. ◇ Konstruktoren public PipedOutputStream() Erzeugung eines PipedOutputStreamObjekts, das an kein PipedInputStreamObjekt (Pipe-Leseseite) gekoppelt ist public PipedOutputStream(PipedInputStream snk) Erzeugung eines PipedOutputStreamthrows IOException Objekts, das an das PipedInputStreamObjekt snk (Pipe-Leseseite) gekoppelt ist. ◇ Memberfunktionen Die Klasse PipedOutputStream stellt das durch ihre Basisklasse OutputStream definierte ZugriffsInterface zur Verfügung public void write(int b) Schreiben des niederwertigen Bytes von b public void write(byte[] buff) Schreiben des Byte-Buffers buff public void write(byte[] buff, int pos, int len) Schreiben von len Bytes aus dem Buffer buff ab der Position pos public void flush() Übergabe aller gepufferten Schreib-Bytes an den Pipe-Buffer public void close() Schliessen des Streams (Pipe-Schreibseite) Zusätzlich ist definiert : public void connect(PipedInputStream snk) Kopplung des aktuellen Objekts an das PipedInputStream-Objekt snk (Pipe-Leseseite) Alle Methoden werfen eine IOException, wenn ein I/O-Fehler auftritt (z.B: Schreibversuch bei fehlender PipeKopplung) • Die Klasse PipedWriter (Package java.io) ◇ Objekte dieser Klasse bilden die Schreibseite einer Zeichen-Stream-Pipe. ◇ Es existieren ▻ analoge Konstruktoren (Default-Konstruktor und Konstruktor zur Kopplung an einPipedReader-Objekt) und ▻ analoge Memberfunktionen (Interface der Basisklasse Writer, sowie Methode connect(...)) wie bei der Klasse PipedOutputStream. FACHHOCHSCHULE MUENCHEN FAKULTÄT ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 743 – 00 – TH – 03 ----------------------------------------------------------------------------------- Thread-Kommunikation über Pipes in Java (3) • Die Klasse PipedInputStream (Package java.io) ◇ Objekte dieser Klasse bilden die Leseseite einer Byte-Stream-Pipe. ◇ Konstruktoren public PipedInputStream() Erzeugung eines PipedInputStreamObjekts, das an kein PipedOutputStreamObjekt (Pipe-Schreibseite) gekoppelt ist public PipedInputStream(PipedOutputStream src) Erzeugung eines PipedInputStreamthrows IOException Objekts, das an das PipedOutputStreamObjekt src (Pipe-Schreibseite) gekoppelt ist. ◇ Memberfunktionen Die Klasse PipedInputStream stellt das durch ihre Basisklasse InputStream definierte ZugriffsInterface zur Verfügung. Allerdings werden die Methoden mark(...) und reset() nicht unterstützt. public int read() Lesen des nächsten Bytes aus der Pipe (==Funktionswert) Funktionswert==-1, wenn Schreibseite geschlossen public int read(byte[] buff) Lesen einer Byte-Folge, Ablage im Buffer buff Funktionswert : Anz. gelesener Bytes bzw –1, wenn Schreibseite geschlossen public int read(byte[] buff, int pos, int len) Lesen von maximal len Bytes, Ablage imBuffer buff ab der Position pos Funktionswert : Anz. gelesener Bytes, bzw –1 (wie oben) public int available() Ermittlung der Anzahl Bytes, die in der Pipe verfügbar sind, d.h. ohne Blockierung gelesen werden können public long skip(long n) Überlesen der nächsten (maximal) n Bytes Funktionswert : Anzahl der tatsächlich überlesenen Bytes public void close() Schliessen des Streams (Pipe-Leseseite) Zusätzlich ist u.a. definiert : public void connect(PipedOutputStream src) Kopplung des aktuellen Objekts an das PipedOutputStream-Objekt src (Pipe-Schreibs.) Alle Methoden werfen eine IOException, wenn ein I/O-Fehler auftritt (z.B: Leseversuch bei fehlender PipeKopplung) • Die Klasse PipedReader (Package java.io) ◇ Objekte dieser Klasse bilden die Leseseite einer Zeichen-Stream-Pipe. ◇ Es existieren ▻ analoge Konstruktoren (Default-Konstruktor und Konstruktor zur Kopplung an einPipedWriter-Objekt) und ▻ analoge Memberfunktionen (Interface der Basisklasse Reader, sowie Methode connect(...)) wie bei der Klasse PipedInputStream. FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 744 – 01 – TH – 01 ----------------------------------------------------------------------------------- Thread-Kommunikation über Pipes in Java (4-1) • Demonstrationsprogramm zur Lösung des Erzeuger-Verbraucher-Problems mittels Pipes ◇ Ein Erzeuger-Thread erzeugt laufend einen zwischen 0 und 1000 liegenden int-Wert und schreibt diesen in eine Pipe. Ein Verbraucher-Thread liest die erzeugten Werte aus der Pipe. ◇ Zur vereinfachten Übertragung der int-Werte in Binärdarstellung werden der Pipe Filter vor- bzw nachgeschaltet : ▻ der Pipe-Schreibseite (PipedOutputStream-Objekt) wird ein DataOutputStream-Objekt vorgeschaltet ▻ der Pipe-Leseseite (PipedInputStream-Objekt) wird ein DataInputStream-Objekt nachgeschaltet ◇ Quellcode der Klasse IntErzeugerPiped (Datei IntErzeugerPiped.java) // IntErzeugerPiped.java import java.io.*; public class IntErzeugerPiped extends Thread { private PipedOutputStream outpipe; private int cnt; public IntErzeugerPiped(PipedOutputStream op, int anz) { outpipe = op; cnt = anz; } public void run() { try { DataOutputStream pout = null; try { int val; pout = new DataOutputStream(outpipe); for (int i=0; i<cnt; i++) { val = (int)(Math.random()*1000); pout.writeInt(val); System.out.println("erzeugt : " + val); try { Thread.sleep((int)(Math.random()*200)); } catch(InterruptedException e) {} } } finally { pout.close(); } } catch(IOException ex) { System.err.println(ex.getMessage()); } } } FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 744 – 02 – TH – 02 ----------------------------------------------------------------------------------- Thread-Kommunikation über Pipes in Java (4-2) • Demonstrationsprogramm zur Lösung des Erzeuger-Verbraucher-Problems mittels Pipes, Forts. ◇ Quellcode der Klasse IntVerbraucherPiped (Datei IntVerbraucherPiped.java) // IntVerbraucherPiped.java import java.io.*; public class IntVerbraucherPiped extends Thread { private PipedInputStream inpipe; private int cnt; public IntVerbraucherPiped(PipedInputStream ip, int anz) { inpipe = ip; cnt = anz; } public void run() { try { int val; DataInputStream pin = new DataInputStream(inpipe); for (int i=0; i<cnt; i++) { val = pin.readInt(); System.out.println(" verbraucht : " + val); try { Thread.sleep((int)(Math.random()*200)); } catch(InterruptedException e) {} } } catch (EOFException ex) { System.out.println(" fertig !!!"); } catch(IOException ex) { System.err.println(ex.getMessage()); } } } FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 744 – 03 – TH – 02 ----------------------------------------------------------------------------------- Thread-Kommunikation über Pipes in Java (4-3) • Demonstrationsprogramm zur Lösung des Erzeuger-Verbraucher-Problems mittels Pipes, Forts. ◇ Quellcode der Klasse PipeDemo (Datei PipeDemo.java) // PipeDemo.java import java.io.*; public class PipeDemo { public static void main(String[] args) throws IOException { PipedOutputStream opipe = new PipedOutputStream(); PipedInputStream ipipe = new PipedInputStream(opipe); new IntErzeugerPiped(opipe, 12).start(); new IntVerbraucherPiped(ipipe, 13).start(); } } ◇ Beispiel für Start und Ausgabe des Programms