Threads

Ein Thread ist ein unabhängiger Pfad der Ausführung innerhalb eines Programms. Allgemein ist es also ein Ausführungsstrang in der Abarbeitung eines Programms. In Java gibt es im Basis-Package java.lang die Klasse Thread. Sie wird benötigt, um Konzepte wie das Multithreading zu ermöglichen.


Zustände

Ein Thread hat verschiedene Zustände. Der Thread Scheduler, der später noch vorgestellt wird, hat die Hoheit darüber, welcher Thread gerade ausgeführt wird, also den Zustand laufend erhält und welche lediglich im lauffähigen Zustand sind.

  • Neu heißt ein neuer Thread wurde erstellt.
  • Lauffähig heißt, der Thread wurde gestartet und kann laufen. Er läuft aber gerade nicht bspw. wegen fehlender CPU – Kapazität.
  • Laufend heißt, der Thread wird ausgeführt.
  • Nicht ausführbar oder blockiert bedeutet der Thread kann momentan nicht laufen.
  • Beendet oder tot heißt er wurde beendet.

Thread-Scheduler

In Java ist der Thread Scheduler Teil der JVM (Java Virtual Machine) und auf verschiedenen JVMs unterschiedlich implementiert. Der Scheduler entscheidet, welche Threads in den Zustand laufend versetzt werden. Er entscheidet auch über den Zeitpunkt und die Dauer des laufenden Threads. Es ist nicht vollkommen vorhersehbar, welche Entscheidung der Scheduler trifft d.h. welcher Thread als nächstes für wie lange in den laufenden Zustand versetzt wird.

Es lassen sich zwei Ratschläge ableiten:

  1. Man sollte sein Programm unabhänig von dem Verhalten des Schedulers machen.
  2. Multithreading-Programme sollten auf mehreren unterschiedlichen JVMs getestet werden.

Multithreading in Java

Multithreading ist ein Konzept in Java, die Ausführung mehrerer paralleler Programme zu ermöglichen. Ein Thread wird daher auch separater Ausführungsstrang oder Aufruf-Stack genannt. Alle Thread-Objekte werden mithilfe der Klasse Thread erzeugt. Die Klasse enthält Methoden um ein Thread-Objekt in verschiedene Zustände zu versetzten. Ein neues Thread-Objekt wird erzeugt durch folgende Zeilen Code:

Thread t = new Thread();
t.start();

Die wichtigsten Methoden der Klasse

  • Void join()
  • Void start()
  • Static void sleep()

In jeder Java-Anwendung wird durch die JVM ein main-Thread gestartet.

Multithreading bedeutet nicht, dass der Prozessor tatsächlich mehrere Programme gleichzeitig laufen lassen kann, aber es wird ein schnelles Umschalten zwischen Programmen ermöglicht. Die JVM wechselt so oft zwischen den Prozessen, bis beide abgeschlossen wurden. Das Betriebssystem muss dafür Java als Prozess ausführen.


Interface Runnable

Runnable ist ein Interface, welches von einer Klasse implementiert wird. Die Klasse beschreibt wiederum, welche Aufgaben der Thread ausführen soll. Die Reihenfolge in der main – Methode:

  • Ein neues Thread-Objekt wird erzeugt.
  • Das Objekt wird an den Konstruktor übergeben, welches parallel ausgeführt werden soll.
  • Die Methode start() des neuen Thread-Objekts wird aufgerufen.

Runnable enthält außerdem die Methode, die zuunterst auf den Stack des neuen Threads kommt, nämlich run(). Der Thread führt die erste Methode aus, die auf den Stack des neuen Threads kommt. Diese ist fest definiert

public void run () {Aufgabe, die ausgeführt werden soll}


Methode: sleep()

Die statische Methode ermöglicht es, Threads trotz des unvorhersehbaren Verhaltens des Thread-Schedulers abwechselnd ihr Programme ausführen zu lassen. Die Methode versetzt den Thread von seinem laufenden Zustand in einen inaktiven Zustand, bevor der Thread wieder in den Zustand lauffähig zurückkehren kann. Dann wartete der Thread darauf, dass der Thread-Scheduler ihn wieder in den laufenden Zustand versetzt. Bei Aufruf der Methode wird die Dauer des „Schlafes“ in Millisekunden festgelegt. Allerdings löst die Methode eine Interrupted Exception aus, daher ist die Verwendung von try-catch  Blöcken obligatorisch.

try {
            Thread.sleep(2000);
}           catch (InterruptesException ex) {
            ex.printStackTrace();
}


Nebenläufigkeitsproblem Dateninkonsistenz

Nebenläufigkeit bezeichnet auch das parallele Ablaufen von Anweisungen oder Befehlen in der Informatik. Dabei kann es zu Dateninkonsistenzen kommen, wenn gleichzeitig unterschiedliche Threads auf die Daten desselben Objektes zugreifen wollen. Um dies zu verhindern, kann man Objekte sperren solange sie bearbeitet werden, sodass kein anderer Thread auf das Objekt zugreifen kann, solange seine run()-Methode noch nicht abgeschlossen ist.

Sperren von Objekten – synchronized

Die Methode eines synchronized Objektes kann nur von einem Thread ausgeführt werden, also nur ein Thread darf gleichzeitig auf eine Methode zugreifen. Die Methode wird zu einer unteilbaren atomaren Einheit. Erst wenn der erste Thread die Methode durchlaufen und abgeschlossen hat, darf ein zweiter Thread auf die Methode zugreifen. So werden kritische Daten geschützt, indem die Methoden synchronisiert werden.

Nachteile – synchronized

Es beeinträchtigt die Performance, da immer nur ein Thread auf eine Methode zugreifen darf.

Außerdem kann es zu einer Deadlock-Situation kommen. Dazu kann man sich folgende Situation vorstellen: Zwei Threads brauchen dieselben zwei Objekte. Wenn jeder den Schlüssel von einem Objekt besitzt, kann man nicht mehr auf Methoden des jeweils anderen Objektes zugreifen. Somit blockieren sie sich gegenseitig.

In Java werden Deadlocks nicht erkannt, auch gibt es keinen Mechanismus zur Beseitigung von Deadlocks.


Nebenläufigkeitsproblem der verlorenen Aktualisierung

Das Problem: Bei komplexeren Problemen, die in mehreren Schritten gelöst werden müssen, wird das Problem deutlich. Wenn zwei Threads gleichzeitig dasselbe Objekt bearbeiten, kann es dazu kommen, dass mitten in seinem Programm der eine Thread zurück in den Zustand lauffähig versetzt wird, der zweite Thread aber schon an dem Objekt weiterarbeitet. Wenn der erste Thread wieder in den laufenden Zustand versetzt wird, macht er genau da weiter, wo er stehen geblieben ist bevor er in den lauffähigen Zustand versetzt wurde. Dadurch überschreibt er gegebenenfalls die Ergebnisse, die der zweite Thread schon erzeugt hat.

Die Lösung: Das Benutzen des Schlüsselwortes synchronized, funktioniert auch in diesem Zusammenhang.


Abhängigkeitsproblem: Erzeuger/ Verbraucher Problem

Beispielsweise, wenn ein Thread für die Erzeugung und Speicherung von Werten in Objekte und ein zweiter Thread für deren Auslesen zuständig ist.

join()
public final void join() : der gerade ausgeführte Thread muss mit der weiteren Ausführung warten, bis der Thread für den die join-Methode ausgeführt wird beendet ist.
wait()
notify()

Die Methoden join, wait, notify und notifyAll bieten in Verbindung mit dem Monitor – Konzept zuverlässigen Schutz für kritische Programme bezüglich Datensicherheit.

Es kann trotzdem zu einer Deadlock-Situation kommen, wenn sich sowohl Erzeuger als auch Verbraucher gleichzeitig im Zustand wait befinden.

Erstelle eine Website wie diese mit WordPress.com
Jetzt starten