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

Multithreading auf der JVM

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

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.

Avatar for Patrick Peschlow

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