Viele aktuelle Prozessoren haben mehr als einen Kern und sind so in der Lage, mehrere Programme gleichzeitig auszuführen. Die Programmiersprache Java ermöglicht es, dies auszunutzen und mehrere Methoden zu starten, die dann gleichzeitig ausgeführt werden. Mann spricht dann von mehreren Threads („Ausführungssträngen“).
Genauer:
Um einen Thread parallel zum Hauptprogramm zu starten schreibt man eine Klasse (z.B. MyTask
), die das Interface Runnable
implementiert und schreibt die Anweisungen, die ausgeführt werden sollen, in ihre run
-Methode:
class MyTask implements Runnable { void run(){ // Die Anweisungen, die hier stehen, werden parallel zum Hauptprogramm ausgeführt. } }
Anschließend übergibt man ein MyTask
Objekt an ein Thread-Objekt und ruft dessen start
-Methode auf:
Thread t = new Thread(new MyTask()); t.start(); // Alle Anweisungen ab hier werden schon parallel zur run-Methode des MyTask-Objekts ausgeführt!
Die start
-Methode führt dann die run
-Methode in einem neuen Thread parallel zum Hauptprogramm aus.
Im folgenden Beispiel gibt es nur ein einziges Objekt der Klasse Counter
. Drei Threads, die gleichzeitig laufen, erhöhen je 1 Million-mal diesen Counter.
Am Ende der Arbeit meldet jeder Thread den aktuellen Zustand des Counters. Man würde erwarten, dass der Thread, der als letztes fertig wird, die Zahl
3000000 ausgibt.
Im rechts stehenden Sequenzdiagramm ist zu sehen, was passiert, wenn zwei Threads gleichzeitig die Methode increment()
des Counter
-Objekts aufrufen. Stellen Sie sich vor, das Attribut counter
hat zu Beginn den Wert 4. Der blaue Thread ist etwas früher dran als der grüne, trotzdem erfolgt die Anweisung counter = i
des blauen Threads erst nach der Anweisung i = counter
des grünen Threads. Das hat zur Folge, dass sowohl die grüne Variable i
als auch die blaue jeweils auf 5 erhöht werden. Am Ende wird zweimal derselbe Wert 5 in die Variable counter
geschrieben, so dass sie folglich nur um eins erhöht wurde, obwohl die Methode increment()
zweimal aufgerufen wurde.
Man nennt diesen Effekt eine race condition. Sie entsteht dadurch, dass die Anweisungen in der Methode increment()
von mehr als einem Thread gleichzeitig ausgeführt werden. Man spricht von einem kritischen Abschnitt.
Wir brauchen eine Möglichkeit, um zu verhindern, dass mehr als ein Thread gleichzeitig den kritischen Abschnitt (in unserem Fall die Methode increment
) betritt. Dies leistet das Schlüssel wort synchronized
bei der Deklaration der Methode increment
. Es weist den Compiler an, Code zu generieren, der zur Laufzeit sicherstellt, dass jeweils nur einen Thread die Methode „betreten“ darf. Nachfolgende Threads werden blockiert (d.h. in der Ausführung angehalten) bis der erste Thread die Methode wieder verlassen hat. Erst dann wird einer der blockierten Threads wieder fortgesetzt.
increment
im folgenden Programm mit der Deklaration im vorangegangenen Programm.