Java Threads Christian Seemann Gliederung 1. Motivation und Zielsetzung 2. Prinzipielle Möglichkeiten der Threadimplementierung in Java 3. Der Lebenszyklus eines Threads in Java 4. Synchronisation von Java Threads 5. Erweiterungen der Basisbibliotheken durch das Package java.util.concurrent 2 Java – Historie und Gegenwart • Java als Programmiersprache von Sun Microsystems • 1995 erstmalige, öffentliche Vorstellung • Zielsetzung: Applikationsentwicklung in einer heterogenen, verteilten Umgebung • Heute: • mehr als 3 Millionen Entwickler • De-facto-Standard für Network-Computing-Anwendungen 3 Multithreadingfähigkeit als wesentliches Designziel • Sicherung der Interaktivität für den Endnutzer • alle Basissystembibliotheken sind „thread safe“ • Potentialvervielfachung der Threadprogrammierung durch kostengünstigen Zugang zu Multiprozessorsystemen 4 Multithreading Paradigma 5 Ziel dieser Seminararbeit • Darstellung und Dokumentation der Threadfähigkeiten von Java • Focus: Basisfunktionalitäten für die parallele Programmierung • Threadgruppen sind außen vorgelassen 6 Vererbung von der Klasse java.lang.Thread • Überschreiben der leeren Methode run() die Aufgaben des Threads zu implementieren • Ausführung eines Threads startet mit Aufruf der Methode public void start() • JVM ruft in diesem Zusammenhang die Methode public void run() des Threads auf 7 Beispiel:Vererbung von der Klasse java.lang.Thread 8 Implementierung des Interfaces java.lang.Runnable • kein Support von Mehrfachvererbung in Java • Nutzung von Threads über das Interface java.lang.Runnable • Überschreiben der Methode public void run() zur Festlegung der Threadaufgaben • Ausführung eines Threads startet mit Aufruf der Methode public void start() • Vorteil: erben weiterer Funktionalität von einer anderen Klasse 9 Beispiel: Implementierung des Interfaces java.lang.Runnable 10 Ein Lebenszyklusmodell new Thread() blocked new monitor lock acquired waiting for monitor lock start() sleep() wait() with timeout join() with timeout timed_waiting Scheduler runnable running yield() interrupt() notify() notifyAll() interrupt() notify() notifyAll() run() wird verlassen wait() join() terminated waiting 11 Erzeugen eines Threads • Signatur des zentralen Konstruktors: • Thread(ThreadGroup group, Runnable target, String name) • Parameter ThreadGroup group: • Ermittlung der Threadgruppe durch Security Manager mittels getThreadGroup() bei Übergabe von null • Zuordnung zur Threadgruppe des Erzeugerthreads, wenn Rückgabewert von getThreadGroup()== null oder kein Security Manager • Parameter Runnable target: • bei Interfaceimplementierung von java.lang.Runnable: • Übergabe einer Objektinstanz der Klasse, die java.lang.Runnable implementiert • Aufruf der Methode run() der durch target spezifizierten Klasse • bei Vererbung von der Klasse java.lang.Thread • Spezifizierung dieses Parameters als null • Aufruf der Methode run() der Klasse, die von Thread erbt, sobald der Thread startet 12 Erzeugen eines Threads [2] • Signatur des zentralen Konstruktors: • Thread(ThreadGroup group, Runnable target, String name) • Parameter String name: Zuordnung eines Namens • Vererbung von Priorität und Dämonstatus seines Erzeugerthreads zum Zeitpunkt der Erzeugung 13 Starten und Ausführen eines Threads • Start eines Threads mit der Methode public void start() • Aufruf der Methode run() des entsprechenden Threads durch die Java VM • Zustandswechsel von „new“ zu Zustand „runnable“ • Kein mehrmaliges Starten eines Threads (auch wenn Thread bereits beendet) • Der Scheduler des Betriebssystems bestimmt die tatsächlich ausführenden Threads (Zustand „running“) 14 Starten und Ausführen eines Threads [2] • Methode public final boolean isAlive(): • Thread gestartet und noch nicht beendet? • Methode public static void yield(): • Pausieren des derzeitig ausführenden Threads [von „running“ zu „runnable“ • andere Threads erhalten die Möglichkeit ihre Aufgaben zu erledigen • Sinnvoll bei langen Kalkulationen eines einzelnen Threads 15 Deaktivieren eines Threads • Explizites Abhalten eines Threads von der Erfüllung seiner Aufgaben • Methoden: join(), sleep(), wait() • Erzeugung einer InterruptedException bei Unterbrechung des deaktivierten Threads durch public void interrupt() • Alle 3 Methoden setzen Interrupted-Status Flag auf false zurück • Je nach Methodenaufruf Zustandswechsel zu • „timed_waiting“ durch Methoden mit Angabe eines TimeoutParameters • „waiting“ durch Methoden ohne Parameterübergabe für einen Timeout 16 Deaktivieren eines Threads – Methode join() • public final void join() public final void join(long millis) public final void join(long millis, int nanos) • Explizites Warten vor Terminierung eines Threads, bis der Zielthread seine Aufgaben vollständig ausgeführt hat • Pausieren des Threads bis zur Terminierung des Zielthreads Beispiel: Demonstration der Methode join() 17 Deaktivieren eines Threads – Methode sleep() • public static void sleep(long millis) • public static void sleep(long millis, int nanos) • Deaktivierung eines Threads für eine bestimmte Zeit • Thread behält alle erlangten Locks 18 Deaktivieren eines Threads – Methode wait() • public final void wait() • public final void wait(long timeout) • public final void wait(long timeout) • public final void wait(long timeout, int nanos) • von Klasse Object vererbt • Ausführung nur bei exklusivem Zugriff auf das Objekt • Aufgabe des Locks sofort nach Beginn der Ausführung der Methode wait() • Koordination von Threads durch wait/notify Mechanismus 19 Deaktivieren eines Threads – Methode wait() [2] • Deaktivierung des Threads bis zum Eintritt eines der folgenden Ereignisse: • ein anderer Thread ruft die Methode public final void notify() für das entsprechende Objekt [für das wait() ausgeführt wurde] auf und der deaktivierte Thread wird zur Ausführung ausgewählt, • ein anderer Thread führt die Methode public final void notifyAll() für das Objekt aus [für das wait() aufgerufen wurde], • ein anderer Thread unterbricht mittels interrupt() den deaktivierten Thread, • die als Parameter angegebene Zeit ist verstrichen [falls keine Zeit angegeben wurde, wartet der Thread bis er mittels notify(), notifyAll() oder interrupt() benachrichtigt wird] 20 Beenden eines Threads • Erreichen des Zustands „terminated“ durch folgende Ereignisse: • Die vollständige Abarbeitung der Methode public void run() • vorzeitige Beendigung von public void run() durch Auftreten einer Ausnahme • Vorzeitige Beendigung mit public void interrupt() • • • setzt Interrupt-Status Flag, falls der zu beendende Thread nicht deaktiviert ist deaktivierte Threads generieren automatisch eine InterruptedException Ausführung des Codeblocks zur Behandlung der Ausnahme [Threadzustand „runnable“ oder „running“] • SecurityException wenn es einem Thread nicht gestattet ist, einen anderen Thread durch Aufruf von interrupt() zu modifizieren • Abfragen des Interrupt-Status Flags mit den Methoden • public static boolean interrupted() [setzt Flag automatisch auf false zurück] • public boolean isInterrupted() Beispiel: Beendigung eines laufenden Threads 21 Aktivieren eines Threads • Zustandsübergang von „waiting“ oder „timed_waiting“ zu „runnable“: • public final void notify() • public final void notifyAll() • Beide Methoden von der Klasse Object vererbt • notify() benachrichtigt einen Thread für das entsprechende Objekt, der mittels wait() deaktiviert wurde • zufällige Auswahl eines Threads bei mehreren wartenden • Keine Garantie, dass der benachrichtigte Thread als nächster den Monitor für das Objekt erhält [Konkurrenz aller nicht deaktivierten Threads] • notifyAll() benachrichtigt alle Threads, die auf den Objekt-Monitor warten • alle Threads bewerben sich um den Objekt-Monitor • Performanznachteil vs. Vorteil der höheren Wahrscheinlichkeit einen ausführbereiten Thread zu finden 22 Weitere Methoden der Klasse Thread Identifikation eines Threads • public long getId() • positive Zahl als ID • Wiederverwendung einer ID nach Beendigung des Threads für einen anderen Thread • public final void setName(String name) • public final String getName() • public String toString() • Liefert Name, Priorität und Threadgruppe eines Threads 23 Weitere Methoden der Klasse Thread Dämonthreads • • • erledigen Aktivitäten im Hintergrund Unterstützung von Threads bei der Ausführung ihrer Aktivitäten Java VM beendet ein Programm, falls nur noch Dämonthreads verbleiben • public final void setDaemon(Boolean on) • Kennzeichnung eines Threads als Dämonthread oder Entzug dieses Status • public final boolean isDaemon() • überprüft, ob ein Thread als Dämonthread ausgeführt wird 24 Weitere Methoden der Klasse Thread – deprecated Methoden • public final void stop() • Aufgabe aller erlangten Locks auf wahrscheinlich noch inkonsistente Objekte • Andere Threads erhalten somit Zugriff auf inkonsistente Objekte • public final void stop(Throwable obj) • Zusätzlich Generierung von Ausnahmen, auf die Zielthread nicht vorbereitet ist • Gefahr von Deadlocks: • public final void suspend() • Thread behält alle erhaltenen Sperren bis zum Aufruf von resume() • public final void resume() • Deadlock falls der Thread, der den Zielthread mit Hilfe von resume() weiterarbeiten lässt, vor dem Aufruf der Methode resume() versucht, die vom Zielthread gehaltene Sperre selbst zu erhalten 25 Weitere Methoden der Klasse Thread im Überblick Methodensignatur Beschreibung public final void checkAccess() Prüft, ob der ausführende Thread die Berechtigung besitzt, diesen Thread zu modifizieren public static Thread currentThread() Liefert eine Referenz auf den derzeit ausführenden Thread public static Boolean holdsLock(Object obj) Liefert wahr, wenn der ausführende Thread das Monitor-Lock für das Objekt obj besitzt public final int getPriority() Liefert die Priorität eines Threads public final ThreadGroup getThreadGroup() Liefert die Threadgruppe des Threads public final void setPriority(int newPriority) Ändert die Priorität eines Threads 26 Basiskonzepte für die Synchronisation • Nutzung von Objekten durch mehrere Threads gemeinsam erfordert Kontrollmechanismen für den Zugriff Synchronisation der beteiligten Threads • Java nutzt das Konzept von Monitoren: • erlauben es jeweils nur einem Thread zu einem Zeitpunkt, bestimmte Codesegmente auszuführen • effiziente Synchronisation nur durch Koordination von Threads • Basismechanismen in Java zur Umsetzung dieser Konzepte: • Schlüsselwort volatile für gemeinsam genutzte Variablen • Schlüsselwort synchronized um Codeabschnitte nur von einem Thread zu einem Zeitpunkt ausführen zu lassen • Koordination von Threads durch den wait/notify Mechanismus 27 Basiskonzepte für die Synchronisation Schlüsselwort volatile • Permanente Haltung der Variablen im Hauptspeicher • Änderungen an dieser Variable sollten von anderen Threads sofort bemerkt werden • Kredel/Yoshida raten jedoch vom Einsatz aufgrund fehlender Zusicherungen ab und empfehlen stattdessen synchronized 28 Basiskonzepte für die Synchronisation Schlüsselwort synchronized • Verwendung sowohl für Methoden als auch für kleinere Codeblöcke • zu einem Zeitpunkt höchstens ein Thread in einem mit synchronized gekennzeichneten Codeabschnitt • Thread erhält ein Lock bei Eintritt auf Ebene des entspr. Objektes oder Klasse • Aufruf weiterer mit synchronized gekennzeichneter Methoden [aus einer synchronized Methode] des gleichen Objektes oder Klasse möglich • Parallele Ausführung von synchronized Methoden verschiedener Objekte derselben Klasse • Performanzeinbussen höher, je größer der synchronisierte Block • Keine Vererbung von synchronized bei entspr. gekennzeichneten Methoden 29 Basiskonzepte für die Synchronisation wait/notify Mechanismus • Koordination von Aktivitäten zwischen mehreren Threads durch gezieltes Zusammenspiel von wait(), notify() und notifyAll() • keine inperformanten, zyklischen Abfragen von Bedingungsvariablen [busy/wait Code] • Versäumnis einer Benachrichtigung [missed notification]: • weil ein Thread noch nicht durch die Methode wait() dafür sensibilisiert wurde • Prüfung, ob Aufruf von wait() notwendig • Verfrühte Benachrichtigung [early notification]: • Berücksichtigung von Bedingungsänderungen, die nach einer Benachrichtigung auftreten • Aufruf von wait() ausschließlich in while-Schleifen Beispiel: Konsumenten/Produzenten Szenario 30 Synchronisation durch Semaphoren • klassische Möglichkeit, um Threads zu synchronisieren • Bestimmen, welche Threads einen spezifischen kritischen Bereich betreten dürfen • beliebig viele Zustände realisierbar [im Gegensatz zu synchronized Codeblöcken, die nur „verschlossen“ und „offen“ erlauben] • Standardimplementierung durch Klasse Semaphore im Package java.util.concurrent Beispiel: Konsumenten/Produzenten Szenario mittels Semaphore 31 Erweiterungen durch das Package java.util.concurrent • Mit Java Version 1.5 ausgeliefert • beinhaltet zahlreiche Basisklassen und einige standardisierte, erweiterbare Frameworks für die parallele Programmierung • ein Framework zur Erzeugung threadartiger Subsysteme zum Management von asynchronen Aufgaben, • das java.util.concurrent.atomic Package für eine hochperformante Manipulation von primitiven Variablen [und Referenzen], • Realisierungen diverser Warteschlangen [Queue, Map und List] und Collections, • Methoden für die Zeitsteuerung im Bereich von Nanosekunden, • allgemeingültige Synchronisationskonzepte [Semaphore, Mutex, Barriere], • das Package java.util.concurrent.locks für die Nutzung flexibler Lockmechanismen. 32 Erweiterungen durch das Package java.util.concurrent [2] • bietet Implementierung mit optimierter Performanz und Zuverlässigkeit • reduziert Programmieraufwand • Lockmechanismen [Vorteile gegenüber synchronized] • nur eine begrenzte Zeit auf die Erlangung eines Locks warten oder einen Thread während dessen zu unterbrechen • Unterstützung mehrerer Bedingungsvariablen pro Lock Beispiel: CubbyHoles mittels Locks 33 Quellenauszug • Hyde, P. (1999): Java Thread Programming: The Authoritative Solution • Kredel, H. / Yoshida, A. (2002): Thread- und Netzwerk-Programmierung mit Java, Praktikum für die Parallele Programmierung • Oaks, S. / Wong, H. (1999): Java Threads, 2nd Edition • Sun Microsystems 3 (2004): Thread (Java 2 Platform SE 5.0), http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Thread.html • Sun Microsystems 4 (1999): Java Thread Primitive Deprecation, http://java.sun.com/j2se/1.5.0/docs/guide/misc/threadPrimitiveDeprecation.html • Sun Microsystems 5 (2005): Concurrent Programming with J2SE 5.0, http://java.sun.com/developer/technicalArticles/J2SE/concurrency/ 34