Slide 1

Slide 1 text

codecentric AG Patrick Peschlow Multithreading auf der JVM

Slide 2

Slide 2 text

codecentric AG Multithreading − Nebenläufiges Abarbeiten von Threads − Kommunikation zwischen Threads ist leichtgewichtig − Effizienz ist begrenzt durch die Nutzung gemeinsamer Ressourcen – Kosten von Context Switching – Zustand von Caches − Tatsächlich parallele Abarbeitung bei geeigneter Hardware – Multicore- bzw. Multiprozessor-Systeme – „Simultaneous Multithreading“-Techniken wie Hyperthreading

Slide 3

Slide 3 text

codecentric AG Die Java Virtual Machine (JVM) − Plattform für Java – und andere kompatible Programmiersprachen − Profiling und Code-/Laufzeitumgebungs-Optimierung − Speicherverwaltung und Garbage Collection − Thread-Management − Bereitstellung diverser Schnittstellen

Slide 4

Slide 4 text

codecentric AG Threads in der JVM − Abbildung von Java-Threads auf native Betriebssystem-Threads – Hohe Skalierbarkeit durch Nutzung von Betriebssystem-Ressourcen – Mechanismen zur Sichtbarkeit notwendig (Java Memory Model) − Verschiedene Arten von Threads – Java-Threads (Programmiermodell) – Native Threads (Anbindung durch JNI) – JVM-interne Threads − Java-Thread-Instanzen -> C++ Thread-Objekte -> native Threads

Slide 5

Slide 5 text

codecentric AG Thread-Lebenszyklus − NEW – Erzeugt, aber noch nicht gestartet − RUNNABLE – Lauffähig, wartet aber auf Betriebssystem-Ressourcen (z.B. Prozessor) − BLOCKED – Wartet auf ein Monitor-Lock (z.B. Aufruf eines synchronized-Block) − WAITING – Wartet auf eine Aktion eines anderen Threads (z.B. notify()) − TIMED_WAITING – Wie WAITING, nur mit Timeout bei zu langer Wartezeit − TERMINATED – Hat seine Ausführung beendet

Slide 6

Slide 6 text

codecentric AG Überblick: Nebenläufige Programmierung in Java − Ursprünglich: „Low-Level“-Ansatz mit Keywords – Explizites Erzeugen und Starten von Threads – synchronized, volatile – wait(), notify(), etc. − Dann: Abstraktion von den Low-Level-Konstrukten durch java.util.concurrent – Runnable-/Task-Ausführung, z.B. mit ExecutorService – Nebenläufige Datenstrukturen, z.B. ConcurrentHashMap – Thread-Synchronisation, z.B. mit Semaphore – Atomare Variablen basierend auf CAS − Aktuell: Noch höhere Abstraktion wird angestrebt – Externe Bibliotheken – Für Java 8 geplante Neuerungen

Slide 7

Slide 7 text

codecentric AG Das Java Memory Model − Herausforderung: Sichtbarkeit – Wann werden Änderungen an Variablen für andere Threads sichtbar? – Schwierig wegen Compiler-Optimierungen (Reordering) und Caches − Das Java Memory Model definiert einen klaren Rahmen – Zentraler Begriff: „happens-before“ – Regeln, unter welchen Umständen „happens-before“ gilt

Slide 8

Slide 8 text

codecentric AG Das Java Memory Model − Vereinfacht gelten zwei Regeln − Verlässt ein Thread A einen synchronized-Block, – und betritt ein anderer Thread B anschließend einen synchronized-Block desselben Monitors, – so sieht B alle Werte von Variablen, die A beim Verlassen gesehen hat − Schreibt ein Thread A eine volatile-Variable, – und liest ein anderer Thread B anschließend dieselbe volatile-Variable, – so sieht B alle Werte von Variablen, die A beim Schreibzugriff gesehen hat

Slide 9

Slide 9 text

codecentric AG Das Java Memory Model Quelle: http://www.ibm.com/developerworks/library/j-jtp03304/

Slide 10

Slide 10 text

codecentric AG Herausforderungen bei Multithreading − Race Conditions – Nicht hinreichend geschützter Zugriff von Threads auf gemeinsame Daten – Ergebnis hängt vom Thread-Scheduling ab und wird nichtdeterministisch – Sind oft nur sehr schwierig zu identifizieren – Unter dem Java Memory Model können Race Conditions dann auftreten, wenn Schreib- und Lesezugriffe mehrerer Threads auf eine Variable nicht durch “happens-before” geordnet sind (z.B. fehlendes synchronized) − Deadlocks und Livelocks – Threads warten ewig auf Ressourcen des/der anderen Threads – Threads befinden sich in einer (ggf. komplexen) Endlosschleife − Die Ursachen sind in der Regel Programmierfehler

Slide 11

Slide 11 text

codecentric AG Beispiel: Sichtbarkeit public class BrokenShutdownExample { public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.start(); // ... do some work ... myThread.shutdown = true; } static class MyThread extends Thread { boolean shutdown = false; @Override public void run() { while (!shutdown) { // ... do some work ... } } } }

Slide 12

Slide 12 text

codecentric AG Garantierte Sichtbarkeit mit volatile public class FixedShutdownExample { public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.start(); // ... do some work ... myThread.shutdown = true; } static class MyThread extends Thread { volatile boolean shutdown = false; @Override public void run() { while (!shutdown) { // ... do some work ... } } } }

Slide 13

Slide 13 text

codecentric AG Synchronization Piggybacking mit volatile int a; int b; volatile boolean initialized = false; public void init() { a = 1; b = 2; initialized = true; } public void check() { while (!initialized) { // just wait } // a == 1 && b == 2 }

Slide 14

Slide 14 text

codecentric AG Beispiel: Race Conditions class BrokenCounter { int counter = 0; int getNext() { return counter++; } }

Slide 15

Slide 15 text

codecentric AG Race Conditions public class BrokenCounterExample { static BrokenCounter counter = new BrokenCounter(); public static void main(String[] args) { new MyThread().start(); new MyThread().start(); } static class MyThread extends Thread { @Override public void run() { for (int i = 0; i < 10; i++) { int next = counter.getNext(); System.out.println(getName() + " " + next); } } } }

Slide 16

Slide 16 text

codecentric AG Race Conditions Thread-1 0 Thread-0 0 Thread-1 1 Thread-0 2 Thread-1 3 Thread-1 5 Thread-0 4 Thread-1 6 Thread-0 7 Thread-1 8 Thread-0 9 Thread-1 10 Thread-0 11 Thread-1 12 Thread-0 13 Thread-1 14 Thread-0 15 Thread-1 16 Thread-0 17 Thread-0 18 public class BrokenCounterExample { static BrokenCounter counter = new BrokenCounter(); public static void main(String[] args) { new MyThread().start(); new MyThread().start(); } static class MyThread extends Thread { @Override public void run() { for (int i = 0; i < 10; i++) { int next = counter.getNext(); System.out.println(getName() + " " + next); } } } }

Slide 17

Slide 17 text

codecentric AG Thread-sicherer Zähler mit synchronized class SynchronizedCounter { int counter = 0; synchronized int getNext() { return counter++; } }

Slide 18

Slide 18 text

codecentric AG Besser: Thread-sicherer Zähler mit AtomicInteger class AtomicCounter { AtomicInteger counter = new AtomicInteger(0); int getNext() { return counter.getAndIncrement(); } }

Slide 19

Slide 19 text

codecentric AG Kosten von synchronized − synchronized-Blöcke (und damit Locks) tauchen manchmal versteckt auf: – Vector vs. ArrayList – Hashtable vs. HashMap – StringBuffer vs. StringBuilder − Empfehlungen: – Verwende nicht-synchronisierte Klassen, wenn nur ein Thread zugreift – Verwende nebenläufige Datenstrukturen für bessere Performance

Slide 20

Slide 20 text

codecentric AG Wissenswertes zu Java Locks − Locks erzeugen versteckten Overhead – Prinzipiell kann jedes Objekt als Lock-Monitor verwendet werden – Zusätzlicher Zustand im Objekt-Header: Wird das Objekt momentan als Lock verwendet, welche Art von Lock ist es, etc. – Der Zugriff auf diesen Zustand muss exklusiv bzw. atomar erfolgen – Höhere Anforderungen an interne Synchronisationsmechanismen der JVM − Dynamische Optimierung von Locks – Umformung von „Thin Locks“ in „Fat Locks“ und umgekehrt – Eliminieren von unnötigen Locks (deaktivierbar mit -XX:-EliminateLocks) – Spekulative Optimierung von Lock- und Unlock-Operationen auf einen bestimmten Thread (deaktivierbar mit -XX:-UseBiasedLocking)

Slide 21

Slide 21 text

codecentric AG Demo: Java Concurrent Animated − Frei verfügbar auf http://sourceforge.net/projects/javaconcurrenta

Slide 22

Slide 22 text

codecentric AG ThreadPoolExecutor vs. ForkJoinPool

Slide 23

Slide 23 text

codecentric AG ThreadPoolExecutor vs. ForkJoinPool

Slide 24

Slide 24 text

codecentric AG Concurrency-Interest − Concurrency JSR-166 Interest Site – http://g.oswego.edu/dl/concurrency-interest/ – Aktuelle Weiterentwicklungen an java.util.concurrent zum Download – Mailingliste für Fragen/Diskussion

Slide 25

Slide 25 text

codecentric AG Lambda Expressions in Java 8 − Vereinfachung mittels Syntax aus der funktionalen Programmierung − Parallelisierung „frei Haus“ − Interne Implementierung von parallel() mit Hilfe von ForkJoin − Performance durch Nutzung von InvokeDynamic int sum = blocks.filter(b -> b.getColor() == BLUE) .map(b -> b.getWeight()) .sum(); int sum = blocks.parallel() .filter(b -> b.getColor() == BLUE) .map(b -> b.getWeight()) .sum();

Slide 26

Slide 26 text

codecentric AG Actors − Nebenläufige Entitäten – Kommunikation über synchrone und asynchrone Nachrichten − Lose Kopplung – Actor-Instanzen können überwacht und bei Bedarf ersetzt werden – Sehr robustes Programmiermodell − Parallele Ausführung von Actors – Scheduling mit Executors oder ForkJoin – Threads sind nicht an einzelne Actors gebunden − Kleine Beispiel-Implementierung für Einsteiger: – http://github.com/peschlowp/ForkJoin

Slide 27

Slide 27 text

codecentric AG Actors: Beispiel Akka public static class Worker extends UntypedActor { public void onReceive(Object message) { if (message instanceof Request) { Response resp = handle((Request) message); getSender().tell(resp, getSelf()); } else { unhandled(message); } } }

Slide 28

Slide 28 text

codecentric AG Bibliotheken für parallele Programmierung − GPars - Groovy Parallel Systems – http://gpars.codehaus.org/ – Parallel Collections, Fork/Join, Dataflow, STM, Actors, … − akka – http://akka.io/ – Actors, Distribution, Supervision

Slide 29

Slide 29 text

codecentric AG Bibliotheken für parallele Programmierung − Highly Scalable Java – http://sourceforge.net/projects/high-scale-lib/ − Disruptor - Concurrent Programming Framework – http://code.google.com/p/disruptor/ − High performance barrier synchronization for Java – http://github.com/peschlowp/jbarrier − und viele mehr...

Slide 30

Slide 30 text

codecentric AG High Performance Computing − In manchen Anwendungen benötigen wir hohe (parallele) Effizienz – z.B. wissenschaftliche Anwendungen, Batch-Anwendungen, HPC – Hier kann sich eine eigene Implementierung lohnen – „Active waiting“ ist ein möglicher Ansatz bei exklusiver Nutzung der Hardware − Beispiel: CyclicBarrier (JDK) vs. CentralBarrier – Gleicher Algorithmus, eigene Implementierung – Bestandteil des „jbarrier“-Pakets

Slide 31

Slide 31 text

codecentric AG Performance-Vergleich

Slide 32

Slide 32 text

codecentric AG Unter der Lupe CyclicBarrier CentralBarrier

Slide 33

Slide 33 text

codecentric AG Thread-Prioritäten − Java unterstützt Thread-Prioritäten – Aber: Nur ein „Hinweis“ für die JVM − Mapping auf native Prioritäten des Betriebssystems ist möglich – (De-)aktivierbar mit -XX:+UseThreadPriorities – Beachte: Der Default-Wert für dieses Flag kann sich ändern − Unterschiedliche Auswirkungen je nach Betriebssystem – Z.B. sind unter Linux root-Rechte nötig − Empfehlung: Thread-Prioritäten in Java nicht verwenden – Kann Performance negativ beeinflussen – Gefährdet Plattformunabhängigkeit

Slide 34

Slide 34 text

codecentric AG Daemon-Threads − Moderne JVMs verwenden viele eigene Threads für interne Aufgaben − Beispiel: HotSpot – VM Thread – VM Periodic Task Thread – Signal Dispatcher – Finalizer – Reference Handler – Low Memory Detector – Attach Listener – Ein oder mehrere Compiler Threads – Je nach Konfiguration noch ein oder mehrere GC-Threads – Ggf. noch Threads für JMX-Server, Connection-Handler, etc.

Slide 35

Slide 35 text

codecentric AG Beendigung der JVM − Aus Sicht einer Java-Anwendung startet die JVM zunächst nur einen einzigen Thread – Dieser führt die main-Methode der Starter-Klasse aus – Im weiteren Verlauf kann die Anwendung prinzipiell beliebig viele weitere Threads erzeugen (und beenden lassen) – Die Anwendungs-Threads leben für die gesamte Laufzeit der Anwendung gemeinsam mit den Daemon-Threads in der JVM − Der Zeitpunkt der Beendigung der JVM wird durch die Anwendungs-Threads bestimmt – Sobald der letzte Anwendungs-Thread beendet ist (durch Verlassen seiner run-Methode), wird auch die JVM beendet – Einzige Ausnahme: Ein Anwendungs-Thread ruft explizit die Methode System.exit bzw. Runtime.exit auf

Slide 36

Slide 36 text

codecentric AG Threads und Speicher JVM-Prozess Gemeinsam genutzter Heap-Speicher Anwendungs- Thread 1 Anwendungs- Thread 2 „Signal Dispatcher“ „Finalizer“ „Reference Handler“ … … Lokaler Stack Lokaler Stack Lokaler Stack Lokaler Stack Lokaler Stack

Slide 37

Slide 37 text

codecentric AG Stack-Konfiguration − Bei JVM-Start kann die Größe der Thread Stacks konfiguriert werden – -Xss legt die Größe für einen Thread Stack fest − Beispiel: -Xss512k legt die Stack-Größe auf 512 KB fest − Default-Werte für x86 Solaris/Linux/Windows – 320 KB bei der 32-bit JVM – 1024 KB bei der 64-bit JVM − Verwendet die Applikation sehr viele Threads, kann es sinnvoll sein, eine kleinere Stack-Größe als den Default-Wert zu verwenden

Slide 38

Slide 38 text

codecentric AG Thread-Local Allocation Buffers (TLABs) − Herausforderung bei Multithreading – Mehrere Threads können gleichzeitig versuchen ein neues Objekt zu erzeugen – Konflikte beim Zugriff auf gemeinsamen Speicher sind teuer − Lösung: TLABs – Jeder Thread erhält einen kleinen exklusiven Teil des gemeinsamen Speichers – Neue Objekte werden dort angelegt – Erst wenn ein TLAB voll wird, werden die Objekte in den gemeinsam genutzten Bereich migriert (und nur dann muss Synchronisation verwendet werden) – Detail-Ausgaben mit -XX:+PrintTLAB

Slide 39

Slide 39 text

codecentric AG Garbage Collection YOUNG PERM Neue und kurzlebige Objekte Langlebige und sehr große Objekte Method Area „Young“/„Minor“ GC OLD „Old“ GC

Slide 40

Slide 40 text

codecentric AG Einige mögliche Abläufe von Garbage Collection serial stop-the-world parallel stop-the-world serial concurrent parallel concurrent

Slide 41

Slide 41 text

codecentric AG Auswahl des Garbage Collectors –XX:+UseParallelGC – Parallele Stop-the-world GC (nur Young) –XX:+UseParallelOldGC – Parallele Stop-the-world GC (Young und Old) –XX:+UseConcMarkSweepGC – Nebenläufige GC (Old), Stop-the-world GC (Young) – Parallelität in allen Phasen unterstützt -XX:+UseG1GC – Weiterentwicklung des CMS Collector (-XX:+UseConcMarkSweepGC)

Slide 42

Slide 42 text

codecentric AG Konfiguration des Garbage Collectors –XX:ParallelGCThreads= – Setzt die Anzahl Threads für Stop-the-World-Phasen – Default ist 3+5N/8 mit N der Anzahl virtueller Prozessoren -XX:ConcGCThreads= – Setzt die Anzahl Threads für nebenläufige Phasen – Default ist (ParallelGCThreads + 3)/4

Slide 43

Slide 43 text

codecentric AG Takeaway − Multithreading auf der JVM – Vielfältige Möglichkeiten für nebenläufige Programmierung – synchronized ist einfach, aber oft unnötig teuer – volatile ist eine gute Alternative bei nur einem schreibenden Thread – java.util.concurrent enthält viele robuste und performante Klassen – Trend zu Einfachheit durch zusätzliche Abstraktion (Lambdas, Actors, DataFlow, …) – Die JVM verwendet eigene Threads für Kompilierung, GC, etc. – Die JVM bietet diverse Optimierungen für Multithreading und Locking − Für weitergehend Interessierte: – Joshua Bloch: „Effective Java“ 2nd edition – Brian Goetz et al.: „Java Concurrency in Practice“ – Charlie Hunt, Binu John: „Java Performance“

Slide 44

Slide 44 text

codecentric AG Fragen? Dr. rer. nat. Patrick Peschlow codecentric AG Merscheider Straße 1 42699 Solingen tel +49 (0) 212.23 36 28 54 fax +49 (0) 212.23 36 28 79 [email protected] www.codecentric.de