Multithreading ab Java 5: Die neuen Concurrency APIs Samstag, 21. November 2009 Java Concurrency •Spezifiziert in JSR166 •Ab Java 5 fester Bestandteil von Java •Durch zusätzliche Library auch vor Java 5 vorhanden ➡ backport-util-concurrent http://backport-jsr166.sourceforge.net/ Samstag, 21. November 2009 Die Pakete und ihre Bedeutung •java.util.concurrent ➡„Wurzel-Paket“ von Standardimplementierungen oftmals benötigter Hilfsmittel in nebenläufigen Programmen •java.util.concurrent.atomic ➡Klassen die das sperrenfreie Programmieren thread-sicherer Verfahren auf einzelne Variablen unterstützt •java.util.concurrent.locks ➡Interfaces und Klassen für differenzierte Sperrenvergabe mittels präzise formulierter Wartebedingungen, die eine Alternative zur Javanativen Synchronisation mittels Monitoren darstellt Samstag, 21. November 2009 Vorteile der Verwendung der Concurrency-Klassen • Reduzierung des Programmieraufwandes Es ist einfacher Standard Klassen zu benutzen als das Rad neu zu erfinden • Erhöhung der Performance: Die Implementationen sind von „Concurrency und Performance Experten“ entwickelt und getestet worden , und somit sind sie „wahrscheinlich“ schneller und skalieren besser als die meisten Implementierungen • Erhöhung der Zuverlässigkeit: Die low-level Concurrency Methoden (wie z.B synchronized, wait(), und notify()) sind oft schwierig korrekt anzuwenden. Fehler sind nicht einfach zu erkennen und zu analysieren Im Gegensatz dazu sind die Concurrency Utilities standardisiert und mehrfach geprüft gegen „Deadlocks“ und „Race Conditions“ Samstag, 21. November 2009 Volatile Schlüsselwort Was bewirkt das „Schlüsselwort“ volatile: •Kurz: Schreiben passiert vor Lesen Immer noch verwirrt? Es gibt 3 Probleme in Java (oder jeder anderen MultiThreading Sprache): ➡Jede CPU (real oder virtuell) kann seinen eigenen kleinen Cache von Speicher haben ➡Jeder Compiler (Javac, oder jeder andere Hotspot Compiler) kann die Reihenfolge des Codes ändern um eine Performancesteigerung zu erreichen ➡Es ist nicht ersichtlich welche Instruktionen in einem anderen Thread bevor oder nach denen in noch einem anderen Thread ausgeführt Samstag, 21. November 2009 Volatile Schlüsselwort -2 Die Auswirkung dieser Probleme: ➡ein „Update“ einer Variablen wird womöglich nicht von einem anderen Thread „rechtzeitig“ gesehen Wie hilft nun „volatile“: ➡Schreib-Operationen werden vor Lese-Operationen durchgeführt ➡Compiler dürfen den Zugriff auf solche Variablen nicht umordnen ➡Caches müssen geleert werden bevor eine solche Variable gelesen wird Samstag, 21. November 2009 Serverbackend-Programmierung Bedienung mehrerer Clientanfragen zugleich Samstag, 21. November 2009 Serverbackend-Programmierung ohne ThreadPools public class ServerBackend { public static void main(String [] args) throws IOException { ServerSocket socket = new ServerSocket(); socket.bind(new InetSocketAddress("localhost",9999)); while(true) { Socket s = socket.accept(); new Thread(new Processor(s)).start(); } } public static class Processor implements Runnable { public Processor(Socket socket) { } public void run() { // handle connection } } } •Probleme: •Neuer Thread für jede Anfrage Server verbraucht die meiste Zeit für das Anlegen und Zerstören von Threads •Beliebig viele Threads möglich Es kann schnell zur Ressourcenüberlastung kommen (Denial of Service) Samstag, 21. November 2009 Serverbackend-Programmierung ab Java 5 durch Verwendung von Thread-Pools public class ServerBackend{ public static void main(String [] args) throws IOException { ExecutorService threadPool = Executors.newFixedThreadPool(100); ServerSocket socket = new ServerSocket(); socket.bind(new InetSocketAddress("localhost",9999)); while(true) { Socket s = socket.accept(); try { threadPool.execute(new Processor(s)); } catch (Exception e) { threadPool.shutdown(); } } } public static class Processor implements Runnable { public Processor(Socket socket) { } public void run() { // handle } } } Ein Threadpool kann beide Probleme lösen •Threads wandern nach der Verwendung zurück in den Pool •Maschinenspezifische Thread-Obergrenze dient zur Prävention überlastbedingter Systemabstürze Samstag, 21. November 2009 Bereitgestellte Thread-Pool-Implementierungen Zur Instantiierung von Thread-Pools wird die Factory-Klasse Executors verwendet, die folgende Typen bereitstellt •FixedThreadPool Ein Threadpool mit festgelegter Anzahl von Threads •CachedThreadPool Dynamischer Pool, dessen Threads auf Anfrage generiert und automatisch wieder gelöscht werden, sobald einzelne Threads länger als 60 Sekunden nicht benötigt werden •ScheduledThreadPool Threadpool dessen Threads nach einem Zeitplan oder periodisch aktiv werden Samstag, 21. November 2009 Callables und Futures • Das Interface Callable ermöglicht den Zugriff auf ein Ergebnis oder auf eine Exception aus einem anderen Thread ➡Wir implementieren call() anstelle von run() •Ein Callable kann an einen Executor übergeben werden ➡Wir rufen submit() anstelle von execute() auf ➡Es wird sofort eine Future-Referenz zurückgeliefert •Wenn das Ergebnis benötigt wird, rufen wir die get-Methode des FutureObjekts auf ➡Solange das Ergebnis berechnet wird, wird der aufrufende Thread blockiert ➡Sobald das Ergebnis vorliegt, wird es zurückgegeben •Ähnlich wie Runnable wird Callable von Klassen implementiert, deren Instanzen nebenläufig arbeiten •Beachte: Runnable liefert kein Ergebnis und kann keine checked Exception werfen Samstag, 21. November 2009 Callable und Futures im Beispiel class Example implements Callable<String> { public String call( ) { // do some work and create a result return result; } public static void main(String[] args) { ExecutorService es = Executors.newSingleThreadExecutor( ); Future<String> f = es.submit(new CallableExample( )); // do some work in parallel try { String callableResult = f.get( ); } catch (InterruptedException ie) { /* ignored purposefully */ } catch (ExcecutionException ee){ /* to be handled ... */ } } Samstag, 21. November 2009 Locks durch Verwendung des Paketes java.util.concurrent.locks Samstag, 21. November 2009 Locks ab Java 5 / java.util.concurrent.locks •Die neuen Klassen in java.util.concurrent.locks stellen eine Alternative zu den Monitormethoden (synchronized) der Object-Klasse dar und ermöglichen eine differenziertere Vergabe von Sperren •Locks sind etwas komplizierter zu bedienen, aber flexibler als die Monitormethoden der Klasse Object ➡Keine Bindung an die Blockstruktur des Programms, wie bei synchronized ➡Es existiert eine nicht-blockierende Variante des Wartens auf ein LockObjekt (polling): boolean tryLock( ); bzw. boolean tryLock(long time, TimeUnit unit); Samstag, 21. November 2009 Locks ab Java 5 / java.util.concurrent.locks bietet unter anderem folgende Implementationen an: •ReentrantLock ➡wechselseitiger Ausschluss wie beim „Monitor-Konzept“ ➡unterstützt „fairness“. Wenn aktiviert werden lang-wartende Threads bei der Lockvergabe bevorzugt •ReentrantReadWriteLock ➡gleich wie ReentrantLock ➡unterstützt Read- und Write-Locks ➡Lock-Zurückstufung. Write-Locks können zu Read-Locks zurück gestuft werden Samstag, 21. November 2009 Lock-freie und Warte-freie Algorithmen mit java.util.concurrent.atomic bietet unter anderen folgende Implementierung an: • AtomicBoolean • AtomicInteger • AtomicIntegerArray • AtomicLong • AtomicLongArray ..... •nutzt „Compare and Set“ -Algorithmus als Ersatz zum Monitor-Konzept ➡Benutzung des „schnellsten“ nativen Codes der verwendeten Plattform Samstag, 21. November 2009 Compare and Set im Detail •nutzt 3 Operanden •Memory Lokation (V) •erwarteter alter Wert (A) •neuer Wert (B) •Ablauf: ➡Lokation V sollte den Wert von A haben ➡Ist das der Fall füge Wert B ein ➡Sollte es nicht der Fall sein ändere V nicht aber sag mir was für ein Wert V hat Samstag, 21. November 2009 Compare and Set im Detail - 2 public class SimulatedCAS { private int value; public synchronized int getValue() { return value; } public synchronized int compareAndSwap(int expectedValue, int newValue) { int oldValue = value; if (value == expectedValue) value = newValue; return oldValue; } } •„Compare and Set“ Ablauf simuliert als Programm Samstag, 21. November 2009 Vergleich von „Synchronized“ und „Atomic“ als Beispiel public class PseudoRandomUsingSynch implements PseudoRandom { private int seed; public PseudoRandomUsingSynch(int s) { seed = s; } public synchronized int nextInt(int n) { int s = seed; seed = Util.calculateNext(seed); return s % n; } } public class PseudoRandomUsingAtomic implements PseudoRandom { private final AtomicInteger seed; public PseudoRandomUsingAtomic(int s) { seed = new AtomicInteger(s); } public int nextInt(int n) { for (;;) { int s = seed.get(); int nexts = Util.calculateNext(s); if (seed.compareAndSet(s, nexts)) return s % n; } } } Samstag, 21. November 2009 Vergleich von „Synchronized“ und „Atomic“ - 2 •Benchmark des Durchsatzes von „klassischen“ synchronized (MonitorKonzept), ReentrantLock, fair Lock, und AtomicLong auf einer 8-way Samstag, 21. November 2009 Vergleich von „Synchronized“ und „Atomic“ - 2 •Vergleich des Durchsatzes von „klassischen“ synchronized (MonitorKonzept), ReentrantLock, fair Lock, und AtomicLong auf einem 1 Prozessor Samstag, 21. November 2009 Semaphoren - nun auch standardisiert Samstag, 21. November 2009 Verwendung von Semaphoren Java 5 bietet mit der Klasse Semaphore erstmals eine standardisierte Implementierung zur Ressourcensynchronisation mittels Zählsemaphoren •Folgendes Beispiel einer Ressourcenklasse zeigt wie Semaphare zu verwenden sind: class SemaphoreExample { private Semaphore available; public SemaphoreExample(int poolSize) { available = new Semaphore(poolSize); } } Samstag, 21. November 2009 public Object getObject() throws InterruptedException { available.acquire(); return new Object(); } public void returnResource(Object r) { available.release(); } Barrieren - Warum manchmal einfach gewartet werden muss Samstag, 21. November 2009 Barrieren •warum Barrieren? ➡manchmal muss erst ein Gruppe von Threads durchlaufen sein bevor ein Programm fortgesetzt werden kann •Vorhandene Implementierungen ➡CyclicBarrier ➡CountDownLatch Samstag, 21. November 2009 Barrieren - CyclicBarrier im Beispiel public class CyclicBarrierExample { public static void main(String[] args) { CyclicBarrier barrier = new CyclicBarrier(2, new FinalRunnable()); for (int i = 0; i < 2; i++) { new Thread(new MyRunnable(barrier)).start(); } } private static class MyRunnable implements Runnable { private CyclicBarrier barrier; public MyRunnable(CyclicBarrier barrier) { this.barrier = barrier; } public void run() { try { barrier.await(); } catch (Exception e) { e.printStackTrace(); } } } private static class FinalRunnable implements Runnable { public void run() { System.out.println("Beide Threads fertig"); } } } Samstag, 21. November 2009 Erzeugung der CyclicBarrier Warte auf andere Threads Alle Threads fertig Barrieren - CountDownLatch im Beispiel public class CountDownLatchExample { public static void main(String[] args) throws InterruptedException { CountDownLatch barrier = new CountDownLatch(2); for (int i = 0; i < 2; i++) { new Thread(new MyRunnable(barrier)).start(); } barrier.await(); System.out.println("Beide Threads fertig"); } Erzeugung der CountDownLatch Warte bis CountDownLatch den Wert 0 hält } private static class MyRunnable implements Runnable { private CountDownLatch barrier; public MyRunnable(CountDownLatch barrier) { this.barrier = barrier; } public void run() { try { barrier.countDown(); } catch (Exception e) { e.printStackTrace(); } } } Samstag, 21. November 2009 Zähle Wert des CountDownLatch um 1 runter Weitere Informationen zum Thema • • http://www.javaconcurrencyinpractice.com/ • http://www.ibm.com/developerworks/java/library/jjtp04186/index.html Samstag, 21. November 2009 http://www.ibm.com/developerworks/java/library/jjtp11234/