• Keine Ergebnisse gefunden

Starten und Beenden von Threads in Java

In Java m¨ussen alle Threads die SchnittstelleRunnableund damit auch die Methode run()implementieren. Threads werden erzeugt und gesteuert ¨uber die KlasseThread.

Da die Klasse Thread selbstRunnableimplementiert, ergeben sich zwei M¨oglichkeiten der Threaderzeugung.

4.6.1 Threaderzeugung mittels Vererbung

Das eine Verfahren besteht darin, eine Klasse von der Klasse Thread abzuleiten und run()zu ¨uberschreiben:

public class MyThread extends Thread { private int instanzVariable;

public MyThread(String threadName, int parameter) { super(threadName);

instanzVariable = parameter;

}

public void run() {

// Hier stehen meine Anweisungen fuer den Thread-Ablauf

weitereMethode();

}

public void weitereMethode() { // Tue was.

}

public static void main(String[] args) { Thread t1 = new MyThread("Thread-A", 7);

Thread t2 = new MyThread("Thread-B", 8);

t1.start();

t2.start();

} }

In main() werden zun¨achst zwei unterschiedliche Objekte einer Klasse erzeugt. Wie bei anderen Klassen auch, kann ich ¨uber den Konstruktor Argumente an das neue Objekt

¨ubergeben. Der Konstruktor initialisiert das Objekt; er startet noch nicht einen besonderen

6Man k¨onnte auf die Idee kommen, dass (0,0) auch dadurch herauskommt, dass der main-Thread nicht die letzten Werte der Variablen sieht. Das ist aber nicht richtig.join()bewirkt, dass die durch den Thread vorgenommenen ¨Anderungen publiziert werden. Ohnejoin()w¨are allerdings (0,0) auch wegen fehlender Sichtbarkeit m¨oglich.

4.6 Starten und Beenden von Threads in Java 59 Thread. Der mittelssuper()an die Oberklasse ¨ubergebene Name ist optional. Er dient nur zu Debuggingzwecken. Wenn er nicht angegeben wird, dann kannsuper() wegfal-len und der Name wird automatisch generiert. Die Initialisierungsanweisungen der beiden Threadobjekte werden noch streng sequentiell durchgef¨uhrt. Der Start der Ausf¨uhrung ei-nes neuen Threads erfolgt erst ¨uber die Aufruf vonstart(). Dies f¨uhrt dazu, dass je-weils ein weiterer Ablauf erzeugt wird. Diese Abl¨aufe k¨onnen sofort mit der Ausf¨uhrung vonrun()beginnen.

4.6.2 Threaderzeugung mittels Delegation

Da Java nur Einfachvererbung kennt, ist die Ableitung von der Klasse Thread nicht immer m¨oglich. Die Ableitung l¨asst sich vermeiden, wenn man die Threadausf¨uhrung an ein Objekt delegiert, das die SchnittstelleRunnableimplementiert.

public class MyRunnable implements Runnable { private int instanzVariable;

public MyRunnable(int parameter) { instanzVariable = parameter;

}

public void run() {

// Hier stehen meine Anweisungen fuer den Thread-Ablauf.

weitereMethode();

}

public void weitereMethode() { // Tue was.

}

public static void main(String[] args) {

Thread t1 = new MyThread("Thread-A", new MyRunnable (7));

MyRunnable r = new MyRunnable(8);

Thread t2 = new MyThread(new MyRunnable(8));

t1.start();

t2.start();

} }

Grunds¨atzlich gelten hier alle Anmerkungen der ersten Variante. Es besteht aber ein deutlich erkennbarer Unterschied zwischen dem Objekten vonMyRunnable und den Thread-Objekten.

Threadobjekte dienen der im engeren Sinn der Verwaltung eines Threadablaufs. Durch Uberschreiben von¨ run()kann zus¨atzliches Verhalten hinzu kommen. Soweit man den Thread als eigenst¨andiges Objekt betrachtet, ist der Ablauf und seine Eigenschaften (Na-me, Priorit¨at, Unterbrechungszustand usw.) gemeint. Im Programm selbst hat man nur wenig mit Thread-Objekten zu tun.

Objekte von Klassen die Runnableimplementieren k¨onnen dazu verwendet werden, mittels ihrer Methoderun() den Anfangspunkt eines Threadablaufs anzugeben. Darin liegt aber genausowenig eine große Besonderheit, wie in einem sequentiellen Programm in der Klassenfunktionmain().

Aus dem Gesagten folgt, dass verschiedene Threads ohne weiteres gleichzeitig Methoden desselben Objekts ausf¨uhren k¨onnen. Es ist dabei nicht n¨otig, dass die gemeinsam genutz-ten Objekte in irgendeiner offensichtlich erkennbaren Weise auf Threads Bezug nehmen.

4.6.3 Beenden von Threads und Ende des Programms

Ein Thread endet mit dem Ende von run(), wenn mit System.exit() das En-de En-der Programmausf¨uhrung erzwungen wird, oEn-der wenn er vor seinem Start per setDemon(true); zu einem

”D¨amonen“ erkl¨art wurde und alle anderen Threads, die keine

”D¨amonen“ sind, beendet sind. ¨Altere Methoden, mit denen man Threads

”abw¨urgen“ konnte, wie stop(), sind wegen ihres fehleranf¨alligen Verhaltens depre-catedund d¨urfen nicht verwendet werden.

Die D¨amon-Eigenschaft ist sinnvoll f¨ur solche Threads, die im Hintergrund n¨utzliche Hilfsaufgaben durchf¨uhren. Dies kann eine Aufgabe, wie die automatische Speicherberei-nigung sein. Sequentielle Programme sind eine einfache Anwendung der angesprochenen Regel. Unbemerkt vom Programmierer verrichten dabei h¨aufig

”D¨amonen“ im Hinter-grund ihre Arbeit.7Der main-Thread, in dem das eigentliche Programm ausgef¨uhrt wird, ist der einzige Nicht-D¨amon. Wenn sein Ende erreicht ist, wird auch das Programm been-det.

Bei graphischen Anwendungen (AWT oder Swing) gibt es einen besonderen Thread zur Kontrolle der gesamten Benutzerinteraktion, dem Event-Thread. Dieser Thread ist kein D¨amon und damit beendet das Ende vonmain()auch nicht das Programm.

Es kann wichtig sein, dass man auf das Ende eines Threads wartet um ¨uber seine Ergeb-nisse verf¨ugen zu k¨onnen. Die einfachste M¨oglichkeit bietet die Methode join()des Threadobjekts. Es ist sichergestellt, dass nachjoin()alle durch den beendeten Thread durchgef¨uhrten Speicher¨anderungen sichtbar sind.

Es ist zu beachten, dassjoin()die gepr¨ufte AusnahmeInterruptedException werfen kann. Dazu wird weiter unten mehr ausgef¨uhrt.

7Die K¨olner Variante von D¨amonen heißtHeinzelm¨annchen.

Kapitel 5

Das Actor-Konzept in Scala

Das Konzept der Nebenl¨aufigkeit von Java hat große Vorteile. Die Implementierung ist sehr einfach und sie hat nur geringe Laufzeit- und Speicherkosten. Das Threading-Konzept mit gemeinsamen Speicher hat aber auch gravierende Nachteile:

– Das Threading-Modell von Java ist nicht objektorientiert. Es ist hervorgegangen aus der prozeduralen Programmierung.

– Die Kommunikation ¨uber gemeinsamen Speicher bringt das Problem der Wettlauf-bedingungenmit sich.

– Der Schutz kritischer Bereiche f¨ugt einem Programm die Gefahr von Verklemmun-gen (deadlock) hinzu.

– Fehler in nebenl¨aufigen Programmen mit gemeinsamem Speicher lassen sich kaum reproduzieren. Die Fehler lassen sich durch Tests kaum entdecken.

– Vielen Entwicklern f¨allt es sehr schwer, threadsichere Programme zu schreiben. Sie neigen dazu, Multithreading zu vermeiden.

Bereits in den 70er Jahren ist im Umfeld der funktionalen Programmierung ein alterna-tives Modell, n¨amlich das Actor-Modell entstanden. Es geht zur¨uck auf Ideen von Carl Hewitt und anderen aus dem Jahre 1973.

Im Folgenden wird das Actor-Modell in der in Scala implementierten Form beschrieben.

5.1 Das Actor-Modell

In seiner reinen Form ist das Actor-Modell ein in sich geschlossenes Konzept f¨ur die nebenl¨aufige Berechnung im Rahmen der funktionalen Programmierung. Es passt damit zun¨achst nicht ganz zu den Konzepten der Objektorientierung. In Scala selbst und auch in der folgenden Darstellung sind ein paar Anpassungen vorgenommen, die hier eine Br¨ucke schlagen.

Aus der Sicht des Actor-Modells kennt die Objektorientierung nur passive Objekte, die von einem ihnen fremden Ausf¨uhrungsfaden zeitweise ins Leben gerufen werden. Das Actor-Modell stellt dagegen so etwas wie ein aktives (Funktions-) Objekt dar.

Definition:

EinActor ist eine in sich abgeschlossene Einheit von Ablauf und von Daten. Ein Actor kann anderen Actoren Nachrichten senden, kann weitere Actoren erzeugen und kann sein Verhalten von Nachrichten abh¨angig machen, die er von anderen Actoren empf¨angt.

Wie Sie sehen, entspricht ein Actor ziemlich genau dem, was man oft vereinfachend als die Aufgabe eines Objekts ansieht. Der entscheidende Unterschied zur

”normalen“ Objek-torientierung ist, dass ein Actor stets selbst entscheidet was er tut. Actoren haben sowohl formale Untersuchungen ¨uber Nebenl¨aufigkeit als auch praktische Anwendungen beein-flusst.

Im Unterschied zu Objekten werden die Aktionen von Actoren nur durch Botschaftsob-jekte und nicht durch Methodenaufrufe vermittelt. Dies ist einfach ein Ergebnis der histo-rischen Entwicklung. Ich werde weiter unten zeigen, wie man diesen Unterschied durch das Muster deraktiven Objekteverwischen kann.