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
    Patrick Peschlow
    Multithreading auf der JVM

    View full-size slide

  2. 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

    View full-size slide

  3. 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

    View full-size slide

  4. 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

    View full-size slide

  5. 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

    View full-size slide

  6. 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

    View full-size slide

  7. 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

    View full-size slide

  8. 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

    View full-size slide

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

    View full-size slide

  10. 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

    View full-size slide

  11. 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 ...
    }
    }
    }
    }

    View full-size slide

  12. 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 ...
    }
    }
    }
    }

    View full-size slide

  13. 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
    }

    View full-size slide

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

    View full-size slide

  15. 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);
    }
    }
    }
    }

    View full-size slide

  16. 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);
    }
    }
    }
    }

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  19. 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

    View full-size slide

  20. 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)

    View full-size slide

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

    View full-size slide

  22. codecentric AG
    ThreadPoolExecutor vs. ForkJoinPool

    View full-size slide

  23. codecentric AG
    ThreadPoolExecutor vs. ForkJoinPool

    View full-size slide

  24. 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

    View full-size slide

  25. 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();

    View full-size slide

  26. 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

    View full-size slide

  27. 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);
    }
    }
    }

    View full-size slide

  28. 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

    View full-size slide

  29. 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...

    View full-size slide

  30. 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

    View full-size slide

  31. codecentric AG
    Performance-Vergleich

    View full-size slide

  32. codecentric AG
    Unter der Lupe
    CyclicBarrier
    CentralBarrier

    View full-size slide

  33. 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

    View full-size slide

  34. 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.

    View full-size slide

  35. 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

    View full-size slide

  36. 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

    View full-size slide

  37. 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

    View full-size slide

  38. 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  41. 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)

    View full-size slide

  42. 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

    View full-size slide

  43. 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“

    View full-size slide

  44. 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

    View full-size slide