Java Concurrency Utilities Java unterstützt seit Java 1.0 Multithreading Java unterstützt das Monitorkonzept mittels der Schlüsselworte synchronized und volatile sowie den java.lang.Object Methoden wait(), notify() und notifyAll() Das ist eine vollständige Multithreading Unterstützung Aber: "Classical multi-threaded programming is very complex and error prone". (Java Tuning White Paper von Sun) Synchronisationskontrukte (z.B. Barriere) wurden in Applikationen immer wieder neu erfunden Eine Alternative zu Eigenbau Lösungen war lange Zeit die als Open Source verfügbare util.concurrent Klassenbibliothek von Doug Lea Basierend auf diesen Erfahrungen wurde 2002 unter der Leitung von Doug Lea der JSR 166 ins Leben gerufen mit dem Ziel "[to] provide functionality commonly needed in concurrent programs" Das Ergebnis sind die sogenannten "Concurrency Utilities", die sich im Package "java.util.concurrent" befinden und die seit Java 5 fester Bestandteil des JDK sind Java Concurrency Utilities Grundidee: fertige Bausteine den Anwendungsentwicklern zur Verfügung zu stellen Bestandteile der Concurrency Utilities: Concurrent Collections: spezielle Implementierungen der Collection Framework Interfaces Map, Set, List, Queue, Deque. Executor Framework: ein Framework zur Ausführung asynchroner Tasks durch Threadpools. Synchronizers Diverse Hilfsklassen zur Koordination mehrerer Threads, zum Beispiel Semaphore oder CyclicBarrier Locks und Conditions: Flexiblere Objektrepräsentation des synchronized Schlüsselworts sowie der java.lang.Object Monitor Methoden wait(), notify() und notifyAll(). Atomic Variables: Erlauben die atomare Manipulation einzelner Variablen (CAS - Compare And Set) zur Implementierung von Algorithmen ohne Sperren (lock-free algorithms). Java Executer Zur parallelen Ausführung eines Runnable ist immer ein Thread notwendig. Obwohl die nebenläufige Abarbeitung von Programmcode ohne Threads nicht möglich ist, sind doch beide sehr stark verbunden, und es wäre gut, wenn das Runnable von dem tatsächlich abarbeitenden Thread etwas getrennt wäre. Das hat mehrere Gründe: Schon beim Erzeugen eines Thread-Objekts muss das Runnable-Objekt im Thread-Konstruktor übergeben werden. Es ist nicht möglich, das Thread-Objekt aufzubauen, dann über eine JavaBean-Setter-Methode das Runnable-Objekt zuzuweisen und anschließend den Thread mit start() zu starten. Wird start() auf dem Thread-Objekt zweimal aufgerufen, so führt der zweite Aufruf zu einer Ausnahme. Ein erzeugter Thread kann also ein Runnable duch zweimaliges Aufrufen von start() nicht gleich zweimal abarbeiten. Für eine erneute Abarbeitung eines Runnable ist also mit unseren bisherigen Mitteln immer ein neues Thread-Objekt nötig. Der Thread beginnt mit der Abarbeitung des Programmcodes vom Runnable sofort nach dem Aufruf von start(). Die Implementierung vom Runnable selbst müsste geändert werden, wenn der Programmcode nicht sofort, sondern später (nächste Tagesschau) oder wiederholt (immer Weihnachten) ausgeführt werden soll. Wünschenswert ist eine Abstraktion, die das Ausführen des Runnable-Programmcodes von der technischen Realisierung (etwa den Threads) trennt. ThreadPool Eine wichtige statische Methode der Klasse Executors ist newCachedThreadPool(). Das Ergebnis ist ein ExecutorService-Objekt, eine Implementierung von Executor mit der Methode execute(Runnable): Java Executer Runnable r1 = new Runnable() { @Override public void run() { System.out.println( "A1 " + Thread.currentThread() ); System.out.println( "A2 " + Thread.currentThread() ); } }; Runnable r2 = new Runnable() { @Override public void run() { System.out.println( "B1 " + Thread.currentThread() ); System.out.println( "B2 " + Thread.currentThread() ); } }; ExecutorService executor = Executors.newCachedThreadPool(); Ausgaben: executor.execute( r1 ); executor.execute( r2 ); Thread.sleep( 500 ); executor.execute( r1 ); executor.execute( r2 ); executor.shutdown(); A1 Thread[pool-1-thread-1,5,main] A2 Thread[pool-1-thread-1,5,main] B1 Thread[pool-1-thread-2,5,main] B2 Thread[pool-1-thread-2,5,main] B1 Thread[pool-1-thread-1,5,main] B2 Thread[pool-1-thread-1,5,main] A1 Thread[pool-1-thread-2,5,main] A2 Thread[pool-1-thread-2,5,main] Threads mit Rückgabe von Werten mittels callable und future Listing 2.13: com/tutego/insel/thread/concurrent/CallableDemo.java, SorterCallable class SorterCallable implements Callable<byte[]> { private final byte[] b; SorterCallable( byte[] b ) { this.b = b; } @Override public byte[] call() { Arrays.sort( b ); return b; // gibt es nicht bei runnable } } Listing 2.14: com/tutego/insel/thread/concurrent/CallableDemo.java, main() Ausschnitt byte[] b = new byte[ 4000000 ]; new Random().nextBytes( b ); Callable<byte[]> c = new SorterCallable( b ); ExecutorService executor = Executors.newCachedThreadPool(); Future<byte[]> result = executor.submit( c ); Der ExecutorService bietet eine submit()-Methode, die das Callable annimmt und einen Thread für die Abarbeitung aussucht. Threads mit Rückgabe von Werte mittels callable und future Listing 2.15: com/tutego/insel/thread/concurrent/CallableDemo.java, main() byte[] b = new byte[ 4000000 ]; new Random().nextBytes( b ); Callable<byte[]> c = new SorterCallable( b ); ExecutorService executor = Executors.newCachedThreadPool(); Future<byte[]> result = executor.submit( c ); byte[] bs = result.get(); // blockiert erst jetzt, falls das Ergebnis noch nicht da ist System.out.printf( "%d, %d, %d%n", bs[0], bs[1], bs[bs.length-1] ); // –128, –128, 127 Vorteil: Ergebnisse von TASKS in Threads ausgeführt können angefordert werden. Blockierungsfreies arbeiten möglich .... Grob- und feingranulares Synchronisieren beim Erzeugen eines Singleton Ein Singleton verhindert mehrfache Erzeugung der Objekte einer Klasse privater Konstruktor verhindert Erzeugung von mehreren Objekten static Methode erlaubt Erzeugung eines einzigen Objektes Objekt wird in privater Variable gespeichert weitere Aufrufe liefern das vorher erzeugte Objekt static Methode muss bei parallelen Zugriff synchronisiert werden //Threadsicherer Singleton public class ST { private static ST instance = null; private ST() {} public static synchronized ST getInstance(){ if (instance == null) { instance = new ST(); } return instance; } //Threadsicherer und schneller Singleton? public class ST { private static ST instance = null; private ST() {} public static ST getInstance(){ if (instance == null) { //first check synchronized(ST.class) { if (instance == null) { //second check instance = new ST(); } } return instance; } Grob- und feingranulares Synchronisieren beim Erzeugen eines Singleton public class ST { //Threadsicherer Singleton private static ST instance = null; private ST() {} public static synchronized ST getInstance(){ if (instance == null){ instance = new ST(); } return instance; } public class ST { //Threadsicherer u. schnell? private static ST instance = null; private ST() {} public static ST getInstance(){ if (instance == null) { //first check synchronized(ST.class) { if (instance == null) { //second check instance = new ST(); } } return instance; }} public class ST { //nicht threadsicher! private static ST instance = null; private ST() {} public static ST getInstance(){ if (instance == null) { synchronized(ST.class) { if (instance == null) { instance = new ST(); } } return instance;}} public class ST { Threadsicher und schnell ! private static volatile ST instance = null; private ST() {} public static ST getInstance(){ if (instance == null) { synchronized(ST.class) { if (instance == null) { instance = new ST(); } } return instance;}}