Java SE 8 Programmer II: Threads

Im Gegensatz zu Runnable kann Callable einen Returnwert von definiertem Typ zurückgeben und eine Exception werfen.
Um die Erzeugung, den Start und das Ende von Runnable und Callable zu verwalten, kann ExecuterService zum Einsatz kommen. Ein ExecuterService wird von der Executors Factory erzeugt und kann einzelne Threads oder einen Thread-Pool verwalten.
Mittels ExecutorService können einzelne (via submit(...)) oder mehrere (via invokeXYZ(Collection<? extends Callable>) gestartet werden. Die von ExecutorService gestarteten Threads können per shutdown() (ordentliches Task-Ende; keine neuen Tasks) und shutdownNow() (sofortiges Ende alles Tasks; gibt eine Liste der pending Tasks zurück) wieder beendet werden.

Ein Objekt von Typ Future repräsentiert ein Resultat einer asynchronen Berechnung.
Bei der Verwendung von get() einer Future-Instanz wartet der aktuelle Thread auf die Antwort.

synchronized und Atomic Variables
Mit synchronized können Methoden und Code Blocks vor gleichzeitigem Zugriff geschützt werden.
Um Zugriffe auf Variablen mit Basisdatentypen zu schützen, können AtomicBoolean, AtomicInteger und AtomicLong verwendet werden. Für vor parallelem Zugriff geschützte Objektreferencen kann AtomicReference<T> verwendet werden.
Die Änderung des Inhalts kann u.. per set(...), getAndSet(...) und compareAndSet(...) vorgenommen werden. Wenn der Wert verändert wird, gibt es die Varianten addAndGet(...), getAndAdd(...), incrementAndGet(), getAndIncrement(), decrementAndGet() und getAndDecrement().
Ausserdem gibt es die Möglichkeit mit accumulateAndGet(), getAndAccumulate(), getAndUpdate() und updateAndGet() Atomic Variables in Lambdas zu verwenden.

CyclicBarrier
Damit lässt sich das Verhalten von join() elegant auf das leistungsfähigere Framework rund um  Threads abbilden. Wichtig ist, darauf zu achten, wieviele Threads bei der Initialisierung der CyclicBarrier angegeben werden.

CopyOnWriteArrayList
Ein Iterator läuft über den Datenstand des Arrays zum Zeitpunkt der Kreation des Iterators. Deshalb werden nie ConcurrentModificationExceptions geworfen. Die Methode remove() ist auf diesem Iterator nicht implementiert, was zu einer UnsupportedOperationException führt.
Alle Methoden von ArrayList funktionieren hier genau gleich. Neu sind addIfAbsent(...) und addAllAbsent(...).

Fork/Join Framework
Dies ist das Framework für Tasks (aka ForkJoinTask), die sich in kleinere Aufgaben aufgeteilt werden können, die dann parallel abgearbeitet werden können. Diese Tasks liegen in einem ForkJoinPool (Implementierung von ExecutorService Interface), der per Default mit der Anzahl der zur Verfügung stehenden Prozessoren skaliert wird.
Hier gibt es zwei Klassen RecursiveAction und RecursiveTask, die Runnable und Callable entsprechen. Beide haben die Methode compute().
Die compute() Methode ist folgendermassen aufgebaut:

Bildschirmfoto 2018-01-29 um 13.52.41.png

Die Tasks innerhalb von compute werden durch eine Variante von invoke() aufgerufen. Wobei die Variante invoke() synchron auf die Fertigstellung wartet.

Probleme bei Tasks
Beim Deadlock blockieren sich zwei Threads gegenseitig, während bei Starvation ein Thread eine Resource hält und nicht freigibt, die ein anderer Thread auch verwenden möchte.
Bei Livelock sind viele Threads damit beschäftigt Aktionen auszulösen, auf die sie gegenseitig hören und reagieren. Das Programm läuft quasi heiss. 
Bei Race conditions ändern verschiedene Threads gleichzeitig die gleichen Werte. Das Resultat lässt sich daher nicht vorhersagen.