====== Nebenläufige Prozesse ======
* Da mit steigender Taktfrequenz von Mikroprozessoren ihre Abwärme zunimmt, stößt die Technik hier an eine Grenze, die nur überwunden werden kann, indem viele Prozessorkerne gleichzeitig an einer Aufgabe arbeiten.
* Von modernen Betriebssystemen wird erwartet, dass sie es ermöglichen, sehr viele Prozesse (meist mehr als Prozessorkerne verfügbar sind) gleichzeitig ablaufen zu lassen.
Welche Herausforderungen dies in der Softwartechnik zur Folge hat und wie die auftretenden Probleme gelöst werden können, erfahren Sie in diesem Kapitel. \\ \\
**Unterkapitel:**
* [[parallelism:monitor:start|Wechselseitiger Ausschluss]]
* [[parallelism:producerconsumer:start|Erzeuger-Verbraucher-Probleme]]
* [[parallelism:deadlocks:start|Deadlocks (Verklemmungen)]]
===== Begriffe =====
* Ein laufendes Programm zusammen mit dem ihm gerade zugeordneten Ressourcen (z.B. Speicherbereiche, offene Dateien, Netzwerkverbindungen) bildet einen **Prozess** (engl.: **process**).
* Innerhalb eines Prozesses gibt es ein- oder mehrere **nebenläufig** (parallel, engl.: **concurrently**) laufende **Ausführungsstränge** (engl.: **Threads**), die die Befehle des Programms abarbeiten.
* Sind mehrere Prozessorkerne vorhanden, so können Threads **gleichzeitig** ausgeführt werden. Nebenläufigkeit liegt aber auch vor, wenn das Betriebssystem dafür sorgt, dass ein Prozessor die Anweisungen mehrerer Threads abwechselnd ausführt (z.B. 10 Anweisungen des ersten Threads, dann 10 des zweiten, dann 10 des ersten usw.).
===== Threads in Java =====
Jedes Objekt der Klasse ''Thread'' besitzt eine Methode ''run'', die dann in einem neuen Thread ausgeführt wird, wenn dieser durch Aufruf der Methode ''start'' gestartet wird. \\ \\
Um Code in der Methode ''run'' unterzubringen, gibt es zwei Möglichkeiten:
* Wir schreiben eine Unterklasse von ''Thread'' und überschreiben die Methode ''run''.
* Wir schreiben eine Klasse (z.B. ''MyRunnable''), die das Interface ''Runnable'' implementiert und damit verpflichtet ist, eine Methode ''run'' zu besitzen. Beim Instanzieren des Thread-Objekts übergeben wir ihm ein ''MyRunnable''-Objekt.
==== Beispiel 1: Überschreiben der run()-Methode ====
{{ :parallelism:thread-class-diagram.svg |}}
===== Die Methode join =====
Ruft man während der Abarbeitung eines Threads (im folgenden: "Thread 1") die Methode ''join'' eines **anderen** Threads (im folgenden: "Thread 2") auf, so wartet Thread 1 so lange, bis Thread 2 beendet ist. Erst dann fährt Thread 1 mit der nächsten Anweisung fort. \\ \\
**Wichtiges Detail:** \\
Das "Warten" von Thread 1 geschieht nicht **aktiv**, indem in einer Wiederholung immer wieder überprüft wird, ob Thread 2 schon beendet ist, sondern **passiv**, d.h. der für Thread 1 reservierte Prozessorkern steht in dieser Zeit für andere Threads zur Verfügung:
==== Beispiel 2: Nutzung von Thread.join ====
==== Beispiel 2: Eigene Klasse, die Runnable implementiert ====
{{ :parallelism:thread-class-diagram_myrunnable.svg |}}
==== Für Interessierte: Implementierung eines Interfaces durch eine Lambda-Function ====
Besitzt ein Interface nur eine einzige Methode, so nennt man es **functional interface**. Das Interface ''Runnable'' ist ein Beispiel dafür. \\
An jeder Stelle, an der ein Objekt einer Klasse erwartet wird, die ein functional interface implementiert, kann stattdessen eine Kurzform verwendet werden, die man **lambda function** nennt. Sie sieht so aus:
(parameter 1, ..., parameter n) -> { Anweisungen }
Lambda functions sind ein Ersatz für die in vielen anderen Programmiersprachen verfügbaren **closures**, unterscheiden sich aber in einem wesentlichen Punkt von diesen: Wird eine **außerhalb** der lambda function/closure deklarierte Variable **innerhalb** der lambda function/closure genutzt, so wird
* im Falle der lambda function ihr Wert zum Zeitpunkt des Starts der Funktion übergeben während
* im Falle der closure eine Referenz übergeben wird.
Bei letzteren kann daher von verschiedenen closures und von außerhalb auf **die identische Variable** zugegriffen werden, während bei ersteren jede lambda function ihre eigene Kopie der Variable erhält und nur auf diese zugreifen kann.