In diesem Workshop geht es um Concurrency-Lösungen in Java jenseits
der klassischen Lock-basierten Synchronisation, d.h. um Alternativen zu
Parallelverarbeitung mit Locks. Wir besprechen das Java Memory Model
und den Fork-Join-Pool (inkl. Ausblick auf "filter/map/reduce for Java"
in Java 8).
Multicore-Prozessoren verwenden aufwendige Caching-Mechnismen, um eine
gute Verarbeitsgeschwindigkeit zu erreichen. Diese Caching-Verfahren
haben dazu geführt, dass die Programmiersprachen ihre Anforderungen
an die so-genannte "cache coherence" reduzieren mussten, d.h. es ist keineswegs
garantiert, dass alle Threads in einer Anwendung eine übereinstimmende
Sicht auf die Daten im Speicher haben. Vielmehr können unterschiedliche
Threads zum selben Zeitpunkt unterschiedliche Werte für dieselben
Speicherzellen (in verschiedenen Caches) sehen. Dort, wo eine kohärente
Sicht auf die Daten benötigt wird, z.B. wenn mehrere Threads über
gemeinsam verwendete Daten kommunizieren, müssen über sogenannte
Memory Barriers Flushes und Refreshes der Prozessor-Caches ausgelöst
werden. Das wird meistens über Locks (bzw. Mutexe) gemacht;
es ist komplex, fehleranfällig und kostet Performance. Deshalb
gibt es Ideen, die klassische "shared mutable state concurrency" durch
einfachere und performantere Alternativen zu ersetzen oder zumindest zu
reduzieren.
In Java und dem JDK gibt es zwei alternative Ansätze: (1) das "lock-free
programming" mit Hilfe von volatile Variablen und Atomics und (2) den Fork-Join-Framework
für das Verteilen von rekursiven Aufgaben auf viele Cores. Im Workshop
werden die beiden alternativen Ansätze diskutiert und im Praxisteil
verwendet.
(1) Wir besprechen das Speichermodell von Java (JMM - Java Memory
Model). Die oben erwähnte "relaxed cache coherence" hat dazu
geführt, dass mit Java 5 das Speichermodell der Sprache überarbeitet
und präzisiert wurde. Die Sprachspezifikation definiert Garantien
bzgl. Atomicity, Visibility und Reordering von Speicherzugriffen, auf die
sich die Java-Programmierer verlassen können und die die virtuelle
Maschine auf das jeweilige Hardware-Memory-Modells abbilden muss.
Die Garantien des Speichermodells sind unerlässlich für das "lock-free
programming", d.h. das Ersetzen von Locks durch Volatiles und Atomics.
(2) Seit Java 5 wurden Anstrengungen unternommen, die Multithread-Programmierung
in Java durch Standardabstraktionen im JDK zu unterstützen; es ist
eine Reihe von Concurrency Utilities im JDK hinzu gekommen. Relativ
neu ist der seit Java 7 verfügbare Fork-Join-Pool, der für die
Parallelverarbeitung von rekursiven, voneinander abhängigen Aufgaben
konzipiert ist. Der Fork.Join-Pool ist deshalb besonders interessant,
weil er eine zentrale Rolle spielt im Zusammenhang mit der Parallelisierung
von Zugriffen auf Sequenzen. Im Rahmen des Java Enhancement Proposal
JEP 107 "Bulk Data Operations for Collections" (auch als "filter/map/reduce
for Java" bekannt) werden für Java 8 Erweiterungen an den bestehenden
Collections im JDK spezifiziert, damit "bulk operations" in Zukunft automatisch
von mehreren Threads parallel ausgeführt werden. Diese neuen
Abstraktionen werden mit Hilfe des Fork-Join-Pools implementiert sein.
Im Workshop wird der Unterschied des Fork-Join-Pools zum herkömmlichen
Thread-Pool erläutert; es wird Einblick in die Implementierung und
Arbeitsweise des Fork-Join-Frameworks gegeben, seine Verwendung anhand
von Beispielen ausprobiert und auf die Performance dieser Alternative eingegangen.
Teil der praktischen Übungen werden Benchmarks sein, in denen die
Performance von Fork-Join-basierten, parallelen Lösungen im Vergleich
zu sequentiellen Lösung gemessen wird. Dieser "Reality Check"
wird zeigen, wie stark die Performance von der Art der Aufgabe und der
jeweiligen Plattform (Hardware, Betriebsystem, JVM) abhängt. |