Vorlesung Betriebssysteme Threads in Java Vortrag im Rahmen der Vorlesung Betriebssysteme an der FH München von Thomas Wöllert ([email protected]) Betriebssysteme – WS 2004/05 Threads in Java T.Wöllert ([email protected]) Inhalt Erzeugen von Threads Erweitern der Klasse „Thread“ Das Interface „Runnable“ Synchronisation Out-of-Sync Beispiel Schlüsselwort „synchronized“ Variabeln „volatile“ „wait()“ und „notify()“ Probleme Weiter bestehende Problemfälle Bekannte Probleme der Klasse „Thread“ „java.util.Timer“ Erzeugen eines „Timer“ und „TimerTask“ Beispielprogramm: Nutzung von „Timer“(n) Betriebssysteme – WS 2004/05 Threads in Java T.Wöllert ([email protected]) Erzeugen von Threads Erweitern der Klasse „Thread“ // Datum Thread class DateThread extends Thread { public void run() { for ( int i = 0; i < 20; i++ ) System.out.println( new Date() ); } } // Zähler Thread class CounterThread extends Thread { public void run() { for ( int i = 0; i < 20; i++ ) System.err.println( i ); } } // Start ThreadDemo public class ThreadDemo { public static void main( String args[] ) { Thread t1 = new DateThread(); t1.start(); Ausgabe: Mon Oct 11 19:06:49 CEST 2004 0 1 2 3 4 5 6 7 8 9 Mon Oct 11 19:06:49 CEST 2004 10 [...] Thread t2 = new CounterThread(); t2.start(); } } Betriebssysteme – WS 2004/05 Threads in Java T.Wöllert ([email protected]) Erzeugen von Threads Das Interface „Runnable“ // Datum Runnable class DateRunnable implements Runnable { public void run() { for ( int i = 0; i < 20; i++ ) System.out.println( new Date() ); } } // Zähler Runnable class CounterRunnable implements Runnable { public void run() { for ( int i = 0; i < 20; i++ ) System.err.println( i ); } } // Start RunnableDemo public class RunnableDemo { public static void main( String args[] ) { Thread t1 = new Thread( new DateRunnable() ); t1.start(); Ausgabe: Mon Oct 11 19:06:49 CEST 2004 0 1 2 3 4 5 6 7 8 9 Mon Oct 11 19:06:49 CEST 2004 10 [...] Thread t2 = new Thread( new CounterRunnable() ); t2.start(); } } Betriebssysteme – WS 2004/05 Threads in Java T.Wöllert ([email protected]) Synchronization Out-of-Sync Beispiel public class IncrementDemo { public class IncThread implements Runnable { // Die inkrement Variabeln private int a = 0; private int b = 0; // Der Name des Thread und das IncrementDemo Objekt private String name = ""; private IncrementDemo incDemo = null; public IncrementDemo() { Thread t1 = new Thread(new IncThread("A", this)); Thread t2 = new Thread(new IncThread("B", this)); public IncThread(String name, IncrementDemo incDemo) { name = name; incDemo = incDemo; } } t1.start(); t2.start(); public void run() { // Inkrementiere 'a' und 'b' 10.000.000 mal for ( int i = 0; i < 10000000; i++ ) { incDemo.inc(); } public void inc() { a++; b++; } // Hole den Wert von 'a' und 'b' int a = incDemo.getA(); int b = incDemo.getB(); public int getA() { return a; } public int getB() { return b; } // Prüfe 'a' und 'b' auf Gleichheit if(a != b) { System.err.println("ERR: " + name + "/" + a + "/" + b); } else { System.out.println("SUCCESS: " + name); } // Startup Main public static void main( String args[] ) { new IncrementDemo(); } } } } Ausgabe: ERR: B/11492019/16066571 ERR: A/15425448/20000000 Betriebssysteme – WS 2004/05 SUCCESS: A SUCCESS: B SUCCESS: B SUCCESS: A Threads in Java ERROR: B/16832558/12230014 ERROR: A/20000000/15397456 T.Wöllert ([email protected]) Synchronization Schlüsselwort „synchronized“ Synchronization auf Methoden public void inc() { public synchronized void inc() { a++; b++; a++; b++; } } Synchronization auf Blöcke // Inkrementiere 10.000.000 mal for ( int i = 0; i < 10000000; i++ ) { // Inkrementiere 10.000.000 mal for ( int i = 0; i < 10000000; i++ ) { incDemo.inc(); synchronized( incDemo ) incDemo.inc(); } } { } Ausgabe: SUCCESS: A SUCCESS: B SUCCESS: A SUCCESS: B SUCCESS: B SUCCESS: A SUCCESS: A SUCCESS: B aber: erhöhte Laufzeit, da die Threads aufeinandere warten müssen Betriebssysteme – WS 2004/05 Threads in Java T.Wöllert ([email protected]) Synchronization Variabeln „volatile“ Es ist nicht gesichert, dass die einzelnen Bytecode-Befehle zum Ändern von langen Datentypen (z.B. long und double), unteilbar sind. Ein Thread kann also mitten in der Änderung einer dieser Variabeln von einem anderen Thread verdrängt werden. Beispiel: LONG Variable long exampleLong = 0x1234567890abcdefL; Zwei Threads wollen nun diese Variabel ändern : Thread 1 auf 0x00000000 00000000L; Thread 2 auf 0xabcdef12 34567890L; Wenn hier Thread 1 beginnt den ersten Teil der long-Variabeln mit 00000000 zu beschreiben und dann Thread 2 seine Arbeit beginnt ist die Variabel fälschlicherweise mit 00000000 90abcdefL initialisiert. Thread 2 beginnt jetzt auch den ersten Teil der Variabeln zu überschreiben was zu folgendem krummen Wert führt : abcdef12 90abcdefL Lösung: „volatile“ volatile long exampleLong = 0x1234567890abcdefL; Betriebssysteme – WS 2004/05 Threads in Java T.Wöllert ([email protected]) Synchronization „wait()“ und „notify()“ Szenario: Thread 1 liefert Daten, die ein Thread 2 verwenden möchte. Thread 2 möchte allerdings nicht in einer kostspieligen Schleife auf diese Daten warten. Beide Threads synchronisieren sich über ein bekanntes Objekt. Hierdurch kann Thread 2 schlafen und der Prozessor andere Aufgaben übernehmen bis Thread 1 mitteilt, dass die Daten vorhanden sind. Thread T1 Code: Thread T2 Code: synchronized ( o ) { // Habe die Daten erzeugt synchronized ( o ) { o.wait(); // Benachrichtige jetzt meinen Wartenden o.notify(); } // Verarbeite Daten [...] } Da sich beide Threads über das Objekt „o“ synchronisieren wird Thread T2 nach „o.notify()“ aufgeweckt und kann die Verarbeitung der nun zur Verfügung stehenden Daten fortsetzen. Achtung: Es ist nicht garantiert, dass der erste mögliche Thread der „o.wait()“ aufgerufen hat, auch der erste ist, der durch „o.notify()“ wieder aufgeweckt wird. Hierfür gibt es „o.notifyAll()“, welches alle am Objekt „o“ wartenden Threads aufweckt ! Betriebssysteme – WS 2004/05 Threads in Java T.Wöllert ([email protected]) Probleme Weiter bestehende Problemfälle Szenario: Thread 1 will über eine, mit Daten gefüllte Liste, iterieren. Zur selben Zeit verändert Thread 2 die Anzahl der Elemente in der Liste. Thread T1 Code: public void iteriere() { public void iteriere() { // Hole Iterator der Liste Iterator it = list.iterator(); // Laufe durch die Liste for( int i = 0; i < list.size(); i++ ) { // Iteriere über die gesamte Liste while(it.hastNext()) { // Bearbeite aktuelles Listenelement [...] } // Bearbeite aktuelles Listenelement [...] } } } Gefahr in beiden Fällen: ● T1 bearbeitet ein Element mit falschen Daten (Änderung durch T2 nicht abgeschlossen) ● T1 versucht ein „NULL“-Element zu bearbeiten (Element wurde gerade von T2 gelöscht) Lösung: ● Sperren einzelner Datenelemente während Änderungen (nicht der ganzen Liste) ● Prüfung ob das erhaltene Listenelement „NULL“ ist. Betriebssysteme – WS 2004/05 Threads in Java T.Wöllert ([email protected]) Probleme Bekannte Probleme der Klasse „Thread“ Auszug aus der Java v5.0 API – Class „Thread“: void destroy() Deprecated. This method was originally designed to destroy this thread without any cleanup. Any monitors it held would have remained locked. However, the method was never implemented. If if were to be implemented, it would be deadlock-prone in much the manner of suspend(). If the target thread held a lock protecting a critical system resource when it was destroyed, no thread could ever access this resource again. If another thread ever attempted to lock this resource, deadlock would result. Such deadlocks typically manifest themselves as "frozen" processes. For more information, see „Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?“. Selbiges gilt für andere Methoden dieser Klasse : ● resume() ● stop() ● suspend() Dies macht es sehr schwierig einen Thread während der Laufzeit zu stoppen, wieder zu starten oder zu zerstören. Lösung: ● Nutzung der „Timer“ und „TimerTask“ Klassen ● Manuelle Steuerung des „Timer“ Threads Betriebssysteme – WS 2004/05 Threads in Java T.Wöllert ([email protected]) „java.util.Timer“ Erzeugen eines „Timer“ und „TimerTask“ // Der zu erfüllende Task class Task extends TimerTask { public void run() { System.out.println( "Doing something." ); } } // Start TimerDemo public class TimerDemo { public static void main( String args[] ) { Timer timer = new Timer(); // Nach 2 Sekunden geht es los timer.schedule( new Task(), 2000 ); // Nach 1 Sekunde geht es los und // dann alle 5 Sekunden timer.schedule( new Task(), 1000, 5000 ); } } ● ● ● ● Erlaubt eine genauere zeitliche Steuerung von Abläufen (Startzeit, Periodenzeit) Es wird keine endlos „while(true)“-Schleife mehr für zyklische Threads benötigt Keine „deprecated“-Methoden in der API Einfachere manuelle Steuerung -> keine „IllegalThreadStateException()“ Betriebssysteme – WS 2004/05 Threads in Java T.Wöllert ([email protected]) „java.util.Timer“ Beispielprogramm: Nutzung von „Timer“(n) Betriebssysteme – WS 2004/05 Threads in Java T.Wöllert ([email protected]) Ende – End – Fin - Conclude Vielen Dank für Eure Aufmerksamkeit ! Fragen ? Betriebssysteme – WS 2004/05 Threads in Java T.Wöllert ([email protected])