Wiederholung: Threads • Threads können „parallel“ ablaufen • In Java sorgt die JVM für die Zuteilung von Rechenzeit an die Threads. • Beispiele für Anwendung: • Webserver verarbeitet parallel eingehende Anfragen • Laufschrift in einer Bildschirmmaske 1 Geschwindigkeitssteigerung durch Threads? • Beispiel: Heap-Sort • Das Herstellen der Heap-Bedingung in verschiedenen Unterbäumen kann in verschiedenen Threads erfolgen. • Auf einem Multiprozessorsystem (und nur dort) führt das zu einer Geschwindigkeitssteigerung • (Sind allerdings zum Zugriff auf das zu sortierende Feld Dateizugriffe erforderlich, kann das den Nutzen der parallele Arbeitsweise wieder zunichte machen.) • Auf einem Einzelprozessorsystem ist eine Geschwindigkeitssteigerung durch parallele Threads nur zu erreichen, wenn beispielsweise: Thread A intensiv rechnet während Thread B intensiv die Festplatte nutzt oder Thread C intensiv vom Netzwerk liest 2 Wiederholung: Threads in Java class XYZ extends Thread { public void run() { ... } } class Hauptprogramm { Thread a = new XYZ(); a.start() } Alternative zum Erben von Thread: Implementieren des Interfaces Runnable 3 Mögliche Probleme bei Parallelität public class Konto { private int Kontostand; ... public void zahleEin(int betrag) { Kontostand = Kontostand - betrag } public void hebeAb(int betrag) { Kontostand = Kontostand - betrag } 4 Möglicher Ablauf Kontostand anfangs = 1234,00 Thread A (500,00 einzahlen) Thread B (100,00 abheben) Kontostand lesen: 1234,00 500,00 addieren Kontostand schreiben: 1734,00 Kontostand lesen: 1734,00 100,00 subtrahieren Kontostand schreiben: 1634,00 5 Oder auch so... Kontostand anfangs = 1234,00 Thread A (500,00 einzahlen) Thread B (100,00 abheben) Kontostand lesen: 1234,00 Kontostand lesen: 1234,00 500,00 addieren Kontostand schreiben: 1734,00 100,00 subtrahieren Kontostand schreiben: 1134,00 6 Zuteilung von Rechenzeit an die Threads • Wie im Beispiel gesehen: • Ohne besondere Vorkehrungen ist nicht vorhersehbar, zu welchem Zeitpunkt welcher Thread gerade welche Operation ausführt. • Folge: Programme mit Threads sind oft schwerer zu testen. 7 Kritische Abschnitte / Monitore • Ein kritischer Abschnitt ist eine Ressource, auf den immer nur ein Prozess zugreifen darf. • Eine typische Art von kritischen Abschnitten sind Variablen, diese dürfen nicht beliebig parallel gelesen und geschrieben werden. • Sperren sind ein Mittel, um den parallelen Zugriff auf Ressourcen zu reglementieren. • Es ist möglich, kritische Abschnitte zu schützen, so dass nur ein Thread zu einem Zeitpunkt darauf zugreifen darf. Dies geschieht mithilfe so genannter Monitore (Locks). 8 Synchronisierter Ablauf Kontostand anfangs = 1234,00 Thread A (500,00 einzahlen) Belegt den Monitor auf das zu ändernde Konto-Objekt Kontostand lesen: 1234,00 500,00 addieren Kontostand schreiben: 1734,00 Thread B (100,00 abheben) Darf nicht zugreifen, da sich Thread A das Objekt per Monitor „reserviert“ hat Thread B reiht sich in Warteliste ein Gibt den Monitor auf das geänderte Konto-Objekt wieder frei Kontostand lesen: 1734,00 100,00 subtrahieren Kontostand schreiben: 1634,00 9 Synchronisation • In Java: Schlüsselwort synchronized • (Ab Java 5: Alternative java.util.concurrent.locks.Lock, hier nicht behandelt) • Für ein Objekt als synchronized definierte Methoden (oder Blöcke) darf immer nur ein Thread zugleich ausführen. • Die anderen Threads kommen in eine Warteschlange, in der alle Threads verwaltet werden, die die synchronized Methode ausführen wollen. • Threads kommen in diese Warteschlange, wenn: • sie eine synchronized Methode aufrufen, während ein anderer Thread auf das Objekt zugreift, • wenn sie in der aufgerufenen, synchronized Methode yield() aufrufen. 10 Beispiel für Monitor public class Konto { private int Kontostand; ... public synchronized void zahleEin(int betrag) { Kontostand = Kontostand - betrag } public synchronized void hebeAb(int betrag) { Kontostand = Kontostand - betrag } 11 Monitorkonzept • Es lassen sich sowohl einzelne Blöcke, als auch ganze Methoden schützen: • Eine ganze Methode kann durch Kennzeichnung von synchronized geschützt werden, z. B.: public synchronized void zahleEin{ ... } • Ein Block kann durch synchronized(ObjektName) { ... } geschützt werden. Hierbei wird der Monitor des Objektes ObjektName belegt. 12 Parallelitätsregeln • Wenn zwei oder mehr Threads auf ein Objekt zugreifen... • ... gibt‘s kein Problem, wenn alle nur lesen. • Wenn jedoch ein Thread den Inhalt des Objektes veränert, so sollten alle Methoden, die auf das Objekt zugreifen, als synchronized deklariert werden. • Wenn eine Methode auf das Ergebnis eines anderen Threads warten muss, um das Objekt zu ändern, dann sollte wait() aufgerufen werden. • Wenn eine synchronized Methode ihr Objekt geändert hat, dann sollten die gerade wartenden Prozesse mit notifyAll() alarmiert werden. 13 Methoden der Klasse Object • Monitor vorübergehend freigeben: • Mit der Methode public final void wait() throws InterruptedException wird der Monitor freigegeben und der Thread geht in einen Wartezustand, bis er von einem anderen Thread durch notify() oder notifyAll() geweckt wird • Nach Wecken setzt der Thread seine Arbeit an gleicher Stelle fort • Wichtig, um auf nicht freie Resourcen zu warten => andere Threads müssen die Chance bekommen, die Ressourcen freizugeben 14 Methoden der Klasse Object • Wird für einen Thread wait() aufgerufen, kommt er in die Wartemenge des Objektes (nicht zu verwechseln mit der bereits erwähnten Warteschlange der Threads, die einen Monitor erlangen wollen!) • Wir haben also zwei Sorten von „warten“ • WAITING: warte auf notify, Thread nimmt nicht an der Vergabe der Rechenzeit teil. • BLOCKED: warte darauf, einen Monitor zu bekommen • Aufwecken von Threads im Status WAITING • Die Methode public final void notify() der Klasse Object benachrichtigt einen Thread aus der Warteliste auf den zum Objekt zugehörigen Monitor (willkürliche Auswahl) • Die Methode public final void notifyAll() der Klasse Object benachrichtigt alle Threads aus der Warteliste, diese werden um den Monitor konkurrieren 15 Datenaustausch zwischen Threads • Wenn Daten zwischen Threads ausgetauscht werden sollen, gibt es Synchronisationsprobleme. • Unser Beispiel: zwei parallele Threads: • Thread Erzeuger erzeugt Daten • Thread Verbraucher verarbeitet (verbraucht) diese • Kommunikation zwischen den beiden Threads erfolgt über eine Klasse Tunnel: Sie soll von einem Thread jeweils ein Paket Daten aufnehmen und an den anderen Thread weitergeben. • Klar ist: Verbraucher-Thread muss warten, bis Daten im Tunnel vorhanden sind. Der Erzeuger-Thread soll erst dann wieder etwas erzeugen, wenn der Verbraucher-Thread aus dem Tunnel gelesen hat (d.h. Tunnel kann genau einen Wert aufnehmen). 16 Handshake-Verfahren: Erzeuger class Erzeuger extends Thread { private Tunnel t; public Erzeuger(Tunnel tunnel) { t = tunnel; } public void run() { for (int i=0; i<5; i++) { t.geben(i); System.out.println("Erzeuger schreibt " + i); try { Thread.sleep((int)(Math.random() * 100)); } catch (InterruptedException e) { } } } } 17 Handshake-Verfahren: Verbraucher class Verbraucher extends Thread { private Tunnel t; public Verbraucher(Tunnel tunnel) { t = tunnel; } public void run() { int wert = 0; for (int i=0; i<5; i++) { wert = t.nehmen(); System.out.println("Verbraucher liest " + wert); } } } 18 Handshake-Verfahren: Tunnel class Tunnel { private int inhalt; private boolean DatenVorhanden = false; } public synchronized int nehmen() { // Liest int aus Tunnel while (DatenVorhanden == false) { try { wait(); } catch (InterruptedException e){} } DatenVorhanden = false; notifyAll(); return inhalt; } public synchronized void geben(int wert) { // Schreibt int in Tunnel while (DatenVorhanden == true) { try { wait(); } catch (InterruptedException e) {} } inhalt = wert; DatenVorhanden = true; notifyAll(); } 19 Handshake-Verfahren: StartKlasse class StartKlasse { public static void main(String[] args) { Tunnel t = new Tunnel(); // Das Objekt mit dem Monitor Erzeuger e = new Erzeuger(t); Verbraucher v = new Verbraucher(t); e.start(); v.start(); } } Ausgabe: Erzeuger schreibt Verbraucher liest Verbraucher liest Erzeuger schreibt Verbraucher liest Erzeuger schreibt Verbraucher liest Erzeuger schreibt Verbraucher liest Erzeuger schreibt 0 0 1 1 2 2 3 3 4 4 Hinweis: nehmen und geben ist synchronisiert, nicht aber die Ausgabe! 20 Deadlocks • Vorsicht! Mit dem beschriebenen Vorgehen kann man mühelos einen sog. Deadlock (Verklemmung) erzeugen: • Es gibt zwei kritische Ressourcen x und y, jede hat einen Monitor. Zwei Threads alpha und beta versuchen beide auf diese Ressourcen zuzugreifen. Jeder dieser Threads kann seine Aufgabe erst dann erfüllen, wenn er die Monitore auf beide Ressourcen erhalten hat. • • • • Mögliche Situation: alpha bekommt Monitor auf x beta bekommt Monitor auf y Beide warten nun vergeblich auf den jeweils anderen Monitor. 21 VE 12 Java Bibliotheken io und util 22 Überblick (Auszug) • • • • • • • • • • • java.applet java.awt java.beans java.io java.lang java.net java.rmi java.security java.sql java.util javax.swing Applet-Programmierung GUI-Programmierung mit AWT JavaBeans™-Entwicklung Ein-/Ausgabe-Operationen, Datenströme fundamentale Klassen (z.B. String) Netzwerkfunktionen Remote Method Invocation Zertifikate, Kryptographie Datenbank-Funktionen Klassen für Datenstrukturen GUI-Programmierung mit Swing • und viele mehr (XML, CORBA, Namensdienste, Sound...) 23 Java API Specification 24 Dateien und Streams java.io.* 25 Motivation Wofür braucht man Ein- und Ausgaben? • Datenaustausch zwischen Mensch und Maschine (trivial ) • für die Kommunikation von Maschinen untereinander (z.B. Rechner Drucker) • um Persistenz von Objekten (also Daten) zu ermöglichen 26 Persistenz • Von Persistenz eines Objektes spricht man: • wenn das Objekt das Ende einer Programmausführung überlebt und • von einem anderen Programm (oder einer anderen Ausführung des gleichen Programms) benutzt werden kann. • Persistent werden Objekte, indem man sie speichert. Speicherort kann das Dateisystem sein oder auch eine Datenbank. 27 Persistenz • Die Beschreibung der Struktur persistenter Objekte wird das Format genannt. • Problem: wie werden Objekte persistent gemacht? • Wie lassen sich solche persistent gemachten Objekte wieder einlesen? • Wie einigen sich Schreiber und Leser auf Formate? • Java-Ansatz: Eine Reihe von Formaten wird in Form von Klassen und Interfaces vordefiniert. Hierzu gehört das eigentliche Format und natürlich auch der schreibende bzw. lesende Zugriff. 28 Ganz unten: Dateisystem • Computer speichern Daten auf verschiedenen Medien (Festplatten, CD, DVD etc.) in Dateisystemen • diese bestehen aus • Dateien (enthalten die eigentlichen Daten) und • Verzeichnisstruktur (organisiert Dateien und hält Info bereit) • Dateien haben Attribute • Name, Typ, Ort, Größe etc • und es gibt spezifische Methoden • • • • neue Dateien erzeugen in Dateien schreiben aus Dateien lesen Dateien löschen... 29 Ganz unten: Dateisystem • Verzeichnisstruktur organisiert Dateien in einem Verzeichnisbaum • und es gibt typische Operationen: • • • • Inhalt eines Verzeichnisses anzeigen Unterverzeichnis anlegen Unterverzeichnis löschen Dateien in Verzeichnis auflisten... 30 Zugriff auf Dateisystem in Java • Natürlich ist eine Datei in Java wieder ein Objekt, und zwar von der Klasse java.io.File • Beachte: Dieses Objekt steht für den Speicherort einer Datei, nicht für den Inhalt der Datei. • Objekte werden erzeugt, um Dateien erzeugen, öffnen und schließen zu können und für Zugriffe auf das Dateisystem import java.io.File public void erzeugeDatei { File datei = new File("Test.txt"); System.out.println("Dateiname: " + datei.getName()); } Ausgabe: Dateiname: Test.txt 31 java.io.File • Konstruktor: • public File(String pathname) throws NullPointerException • Erzeugt Objekt über Pfadnamen • Prüfung von Eigenschaften: • • • • • • • public public public public public public public boolean exists() boolean canRead() boolean canWrite() boolean isFile() boolean isDirectory() boolean isHidden() long length() 32 java.io.File • Methoden: • public boolean delete() • Löscht Datei oder Verzeichnis • public void deleteOnExit() • Löscht bei Beendigung der Virtual Machine • Flag wird gesetzt, welches nicht zurückgenommen werden kann • public boolean mkdir() • Erstellt Verzeichnis • public boolean mkdirs() • Erstellt Verzeichnis und eventuell benötigte, nicht existierende Mutterverzeichnisse 33 java.io.File • public String[] list() • liefert Array mit Auflistung aller Einträge in Verzeichnis, das durch Namen des File-Objekts gegeben ist • public String[] list(FilenameFilter filter) • liefert Array mit Auflistung aller Einträge in Verzeichnis, das durch Namen des File-Objekts gegeben ist • diese Auflistung ist jedoch gefiltert, dies wird von filter übernommen • filter ist Objekt einer Klasse, die das Interface FilenameFilter implementiert 34 Gefilterte Dateiliste • Interface FilenameFilter kann von einer Klasse implementiert werden • dadurch lässt sich das Ergebnis von File.list() beeinflussen • Inhalt des Interfaces FilenameFilter: public interface FilenameFilter { boolean accept(File dir, String name); } • Es reicht, die accept()-Methode sinnvoll zu implementieren 35 Gefilterte Dateiliste - Beispiel • Beispiel: Filter, der nur Dateinamen durchlässt, die eine bestimmte Zeichenkette enthalten public class DirFilter implements FilenameFilter { String afn; DirFilter(String afn) { this.afn = afn; } public boolean accept(File dir, String name) { String f = new File(name).getName(); return f.indexOf(afn) != -1; } } 36 Gefilterte Dateiliste - Beispiel • Verwendung des Filters: File path = new File("."); String[] list; list = path.list(new DirFilter("xyz")); • sorgt dafür, dass in list[] nur die Dateinamen aus dem aktuellen Verzeichnis („.“) aufgenommen werden, die „xyz“ enthalten. 37