Multithreading ab Java 5: Die neuen Concurrency APIs
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/
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
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
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 Multi- Threading 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
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
Serverbackend-Programmierung Bedienung mehrerer Clientanfragen zugleich
Serverbackend-Programmierung ohne Thread- Pools Probleme: 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 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)
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
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
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 Future- Objekts 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
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... */
Locks durch Verwendung des Paketes java.util.concurrent.locks
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 Lock- Objekt (polling): boolean trylock( ); bzw. boolean trylock(long time, TimeUnit unit);
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
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
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
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
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;
Vergleich von Synchronized und Atomic - 2 Benchmark des Durchsatzes von klassischen synchronized (Monitor- Konzept), ReentrantLock, fair Lock, und AtomicLong auf einer 8-way
Vergleich von Synchronized und Atomic - 2 Vergleich des Durchsatzes von klassischen synchronized (Monitor- Konzept), ReentrantLock, fair Lock, und AtomicLong auf einem 1 Prozessor
Semaphoren - nun auch standardisiert
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); public Object getobject() throws InterruptedException { available.acquire(); return new Object(); public void returnresource(object r) { available.release();
Barrieren - Warum manchmal einfach gewartet werden muss
Barrieren warum Barrieren? manchmal muss erst ein Gruppe von Threads durchlaufen sein bevor ein Programm fortgesetzt werden kann Vorhandene Implementierungen CyclicBarrier CountDownLatch
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(); Erzeugung der CyclicBarrier Warte auf andere Threads private static class FinalRunnable implements Runnable { public void run() { System.out.println("Beide Threads fertig"); 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(); Zähle Wert des CountDownLatch um 1 runter
Weitere Informationen zum Thema http://www.javaconcurrencyinpractice.com/ http://www.ibm.com/developerworks/java/library/jjtp11234/ http://www.ibm.com/developerworks/java/library/jjtp04186/index.html