Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Multithreading auf der JVM

Multithreading auf der JVM

(In German) Presentation held at the Parallel Conference in Karlsruhe, Germany, on May 15, 2013. Apart from the slides, the presentation featured a demo of Java Concurrent Animated.

Patrick Peschlow

May 15, 2013
Tweet

More Decks by Patrick Peschlow

Other Decks in Technology

Transcript

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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 ... } } } }
  10. 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 ... } } } }
  11. 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 }
  12. 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); } } } }
  13. 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); } } } }
  14. codecentric AG Thread-sicherer Zähler mit synchronized class SynchronizedCounter { int

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

    AtomicInteger counter = new AtomicInteger(0); int getNext() { return counter.getAndIncrement(); } }
  16. 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
  17. 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)
  18. codecentric AG Demo: Java Concurrent Animated − Frei verfügbar auf

    http://sourceforge.net/projects/javaconcurrenta
  19. 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
  20. 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();
  21. 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
  22. 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); } } }
  23. 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
  24. 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...
  25. 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
  26. 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
  27. 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.
  28. 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
  29. 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
  30. codecentric AG Stack-Konfiguration − Bei JVM-Start kann die Größe der

    Thread Stacks konfiguriert werden – -Xss<size> 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
  31. 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
  32. codecentric AG Garbage Collection YOUNG PERM Neue und kurzlebige Objekte

    Langlebige und sehr große Objekte Method Area „Young“/„Minor“ GC OLD „Old“ GC
  33. codecentric AG Einige mögliche Abläufe von Garbage Collection serial stop-the-world

    parallel stop-the-world serial concurrent parallel concurrent
  34. 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)
  35. codecentric AG Konfiguration des Garbage Collectors –XX:ParallelGCThreads=<value> – Setzt die

    Anzahl Threads für Stop-the-World-Phasen – Default ist 3+5N/8 mit N der Anzahl virtueller Prozessoren -XX:ConcGCThreads=<value> – Setzt die Anzahl Threads für nebenläufige Phasen – Default ist (ParallelGCThreads + 3)/4
  36. 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“
  37. 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