Präsentation der Java Concurrency

Werbung
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/
Herunterladen