Rheinwerk Computing < openbook >


 
Inhaltsverzeichnis
Materialien zum Buch
Vorwort
1 Java ist auch eine Sprache
2 Imperative Sprachkonzepte
3 Klassen und Objekte
4 Arrays und ihre Anwendungen
5 Der Umgang mit Zeichen und Zeichenketten
6 Eigene Klassen schreiben
7 Objektorientierte Beziehungsfragen
8 Schnittstellen, Aufzählungen, versiegelte Klassen, Records
9 Ausnahmen müssen sein
10 Geschachtelte Typen
11 Besondere Typen der Java SE
12 Generics<T>
13 Lambda-Ausdrücke und funktionale Programmierung
14 Architektur, Design und angewandte Objektorientierung
15 Java Platform Module System
16 Die Klassenbibliothek
17 Einführung in die nebenläufige Programmierung
18 Einführung in Datenstrukturen und Algorithmen
19 Einführung in grafische Oberflächen
20 Einführung in Dateien und Datenströme
21 Einführung ins Datenbankmanagement mit JDBC
22 Bits und Bytes, Mathematisches und Geld
23 Testen mit JUnit
24 Die Werkzeuge des JDK
A Java SE-Module und Paketübersicht
Stichwortverzeichnis


Buch bestellen
Ihre Meinung?



Spacer
<< zurück
Java ist auch eine Insel von Christian Ullenboom

Einführung, Ausbildung, Praxis
Buch: Java ist auch eine Insel


Java ist auch eine Insel

Pfeil 17 Einführung in die nebenläufige Programmierung
Pfeil 17.1 Nebenläufigkeit und Parallelität
Pfeil 17.1.1 Multitasking, Prozesse und Threads
Pfeil 17.1.2 Threads und Prozesse
Pfeil 17.1.3 Wie nebenläufige Programme die Geschwindigkeit steigern können
Pfeil 17.1.4 Was Java für Nebenläufigkeit alles bietet
Pfeil 17.2 Existierende Threads und neue Threads erzeugen
Pfeil 17.2.1 Main-Thread
Pfeil 17.2.2 Wer bin ich?
Pfeil 17.2.3 Die Schnittstelle Runnable implementieren
Pfeil 17.2.4 Thread mit Runnable starten
Pfeil 17.2.5 Runnable parametrisieren
Pfeil 17.2.6 Die Klasse Thread erweitern
Pfeil 17.3 Thread-Eigenschaften und Zustände
Pfeil 17.3.1 Der Name eines Threads
Pfeil 17.3.2 Die Zustände eines Threads *
Pfeil 17.3.3 Schläfer gesucht
Pfeil 17.3.4 Wann Threads fertig sind
Pfeil 17.3.5 Einen Thread höflich mit Interrupt beenden
Pfeil 17.3.6 Unbehandelte Ausnahmen, Thread-Ende und UncaughtExceptionHandler
Pfeil 17.3.7 Der stop() von außen und die Rettung mit ThreadDeath *
Pfeil 17.3.8 Ein Rendezvous mit join(…) *
Pfeil 17.3.9 Arbeit niederlegen und wieder aufnehmen *
Pfeil 17.3.10 Priorität *
Pfeil 17.4 Der Ausführer (Executor) kommt
Pfeil 17.4.1 Die Schnittstelle Executor
Pfeil 17.4.2 Glücklich in der Gruppe – die Thread-Pools
Pfeil 17.4.3 Threads mit Rückgabe über Callable
Pfeil 17.4.4 Erinnerungen an die Zukunft – die Future-Rückgabe
Pfeil 17.4.5 Mehrere Callable-Objekte abarbeiten
Pfeil 17.4.6 CompletionService und ExecutorCompletionService
Pfeil 17.4.7 ScheduledExecutorService: wiederholende Aufgaben und Zeitsteuerungen
Pfeil 17.4.8 Asynchrones Programmieren mit CompletableFuture (CompletionStage)
Pfeil 17.5 Zum Weiterlesen
 

Zum Seitenanfang

17.2    Existierende Threads und neue Threads erzeugen Zur vorigen ÜberschriftZur nächsten Überschrift

Die folgenden Abschnitte verdeutlichen, wie der nebenläufige Programmcode in ein Runnable verpackt und dem Thread zur Ausführung vorgelegt wird.

 

Zum Seitenanfang

17.2.1    Main-Thread Zur vorigen ÜberschriftZur nächsten Überschrift

Jeder Programmcode in Java läuft immer in einem Thread. Wenn die JVM startet, erzeugt sie automatisch einen Thread, der Main-Thread genannt wird. Er arbeitet die main(…)-Methode ab. Das heißt, alles, was wir in die main(…)-Methode setzen, wird von dem Main-Thread abgearbeitet. In einem Debugger lässt sich das gut nachvollziehen.

In der Regel gibt es in der Java-Umgebung noch weitere Threads, die vom System mithilfe von Bibliotheken gestartet werden. Öffnet Java ein Fenster, gibt es einen GUI-Thread, in dem Ereignisse abgearbeitet werden.

 

Zum Seitenanfang

17.2.2    Wer bin ich? Zur vorigen ÜberschriftZur nächsten Überschrift

In Java wird ein Thread über die Klasse java.lang.Thread repräsentiert. Er verbindet die Java-Seite mit der Betriebssystemseite.

Die Klasse Thread liefert mit der statischen Methode currentThread() die Objektreferenz für das Thread-Exemplar, das diese Anweisung gerade ausführt. Auf diese Weise lassen sich nichtstatische Thread-Methoden wie toString() verwenden.

[zB]  Beispiel

Rufe toString() auf dem aktuell laufenden Thread auf:

public static void main( String[] args ) {

System.out.println( Thread.currentThread() ); // Thread[main,5,main]

}

Die String-Repräsentation besteht aus dem Thread-Namen, der Priorität und einer zugehörigen Thread-Gruppe.

Falls es in einer Schleife wiederholten Zugriff auf Thread.currentThread() gibt, sollte das Ergebnis zwischengespeichert werden, denn der Aufruf ist nicht ganz billig.

class java.lang.Thread

implements Runnable
  • static Thread currentThread()

    Liefert den Thread, der das laufende Programmstück ausführt.

 

Zum Seitenanfang

17.2.3    Die Schnittstelle Runnable implementieren Zur vorigen ÜberschriftZur nächsten Überschrift

Damit der Thread weiß, was er ausführen soll, müssen wir ihm Anweisungsfolgen geben. Diese werden in einem Befehlsobjekt vom Typ Runnable verpackt und dem Thread übergeben. Wird der Thread gestartet, arbeitet er die Programmzeilen aus dem Befehlsobjekt nebenläufig zum restlichen Programmcode ab. Die funktionale Schnittstelle Runnable schreibt nur eine run()-Methode vor (siehe Abbildung 17.3).

interface java.lang.Runnable
  • void run()

    Implementierende Klassen realisieren die Operation und setzen dort den nebenläufig auszuführenden Programmcode ein.

UML-Diagramm der einfachen Schnittstelle »Runnable«

Abbildung 17.3     UML-Diagramm der einfachen Schnittstelle »Runnable«

Wir wollen zwei Threads angeben, wobei der eine zwanzigmal das aktuelle Datum und die Uhrzeit ausgibt und der andere einfach eine Zahl:

Listing 17.1     src/main/java/com/tutego/insel/thread/DateCommand.java

package com.tutego.insel.thread;



public class DateCommand implements Runnable {

@Override public void run() {

Stream.generate( LocalDateTime::now ).limit( 20 ).forEach( System.out::println );

}

}

Listing 17.2     src/main/java/com/tutego/insel/thread/CounterCommand.java

package com.tutego.insel.thread;



class CounterCommand implements Runnable {

@Override public void run() {

IntStream.range( 0, 20 ).forEach( System.out::println );

}

}

Unser nebenläufig auszuführender Programmcode in run() besteht aus einem limitierten Stream. Im ersten Fall gibt das Programm 20 aktuelle Datumswerte aus und im anderen Fall einen Schleifenzähler.

 

Zum Seitenanfang

17.2.4    Thread mit Runnable starten Zur vorigen ÜberschriftZur nächsten Überschrift

Nun reicht es nicht aus, einfach die run()-Methode einer Klasse direkt aufzurufen, denn dann wäre nichts nebenläufig, sondern wir würden einfach eine Methode sequenziell ausführen. Damit der Programmcode neben der eigentlichen Applikation läuft, müssen wir ein Thread-Objekt mit dem Runnable verbinden und dann den Thread explizit starten. Dazu übergeben wir dem Konstruktor der Klasse Thread eine Referenz auf das Runnable-Objekt und rufen start() auf. Nachdem start() für den Thread eine Ablaufumgebung geschaffen hat, ruft es intern selbstständig die Methode run() genau einmal auf. Läuft der Thread schon, so löst ein zweiter Aufruf der start()-Methode eine IllegalThreadStateException aus:

Listing 17.3     src/main/java/com/tutego/insel/thread/FirstThread.java, main()

Thread t1 = new Thread( new DateCommand() );

t1.start();



Thread t2 = new Thread( new CounterCommand() );

t2.start();

Beim Starten des Programms erfolgt eine Ausgabe auf dem Bildschirm, die so aussehen kann:

0

1

2

3

4

5

6

7

8

2017-02-17T13:40:35.315250400

2017-02-17T13:40:35.327260600

2017-02-17T13:40:35.327260600

2017-02-17T13:40:35.327260600

9

10

11

12

13

14

15

16

17

18

19

2017-02-17T13:40:35.327260600

...

In meiner Ausgabe ist die Verzahnung der beiden Threads zu erkennen. Zwar ist innerhalb eines Threads durch die Schleife die Reihenfolge klar definiert, doch wann welcher Thread an der Reihe ist, ist dem Betriebssystem freigestellt. So sollte es auch nicht verwundern, dass die erste Zeile in meiner Ausgabe vom Zähl-Thread kommt – dem eigentlich zweiten Thread. Das zeigt deutlich den Nichtdeterminismus[ 249 ](»Nicht vorhersehbar«; bedeutet hier: Wann der Scheduler den Kontextwechsel vornimmt, ist unbekannt. ) bei Threads. Interpretiert werden kann die Ausgabe durch die unterschiedlichen Laufzeiten, die für die Datums- und Zeitausgabe nötig sind: Bei der Datumsverarbeitung sind viel mehr Objekte nötig; sie aufzubauen, dauert. Aber das ist ein Internum, welches bei der Reihenfolge nicht beachtet werden darf. Wer Thread-Ergebnisse in bestimmten Reihenfolgen erwartet, muss Threads synchronisieren.

class java.lang.Thread

implements Runnable
  • Thread(Runnable target)

    Erzeugt einen neuen Thread mit einem Runnable, das den nebenläufig auszuführenden Programmcode vorgibt.

  • void start()

    Ein neuer Thread – neben dem Thread, der die Methode aufruft – wird gestartet. Der neue Thread führt die run()-Methode nebenläufig aus. Jeder Thread kann nur einmal gestartet werden.

[+]  Tipp

Wenn ein Thread im Konstruktor einer Runnable-Implementierung gestartet wird, sollte die Arbeitsweise bei der Vererbung beachtet werden. Nehmen wir an, eine Klasse leitet von einer anderen Klasse ab, und der Konstruktor der Oberklasse startet einen Thread. Bildet die Applikation ein Exemplar der Unterklasse, so werden bei der Bildung des Objekts immer erst die Konstruktoren der Oberklasse aufgerufen. Dies hat zur Folge, dass der Thread schon läuft, auch wenn das Objekt noch nicht ganz gebaut ist. Die Erzeugung ist erst abgeschlossen, wenn nach dem Aufruf der Konstruktoren der Oberklassen der eigene Konstruktor vollständig abgearbeitet wurde.

 

Zum Seitenanfang

17.2.5    Runnable parametrisieren Zur vorigen ÜberschriftZur nächsten Überschrift

Die run()-Methode von Runnable hat keine Parameterliste und keine Rückgabe. Daher stellt sich die Frage, wie Informationen in das Runnable hineinkommen und wieder herauskommen. Dass run() keine Parameterliste hat, ist schnell dadurch erklärt, dass ja völlig unbekannt ist, welche Parameter es überhaupt gibt.

Es gibt zwei einfache Möglichkeiten, innerhalb von run() auf Parameter zuzugreifen:

  • Implementiert eine Klasse die Schnittstelle Runnable, kann sich ein Konstruktor die Werte in internen Zuständen merken. Ruft der Thread run() auf, kann die Methode auf die Werte zurückgreifen.

  • Realisiert ein Lambda-Ausdruck die funktionale Schnittstelle Runnable, so kann der Rumpf auf lokale Variablen aus dem Kontext zugreifen.

Das Ablegen von Ergebnissen ist schwieriger, als man auf den ersten Blick vermutet. Das liegt an dem Problem, dass es nebenläufige Schreibzugriffe geben könnte, und daher sind Synchronisierungsmechanismen nötig. Grundsätzlich sind auch hier zwei Möglichkeiten denkbar:

  • Eine Runnable-Implementierung kommt zu einem Ergebnis und legt es in einem eigenen Zustand ab, der später von außen erfragt wird.

  • run() schreibt in nicht selbst verwalteten Speicher.

 

Zum Seitenanfang

17.2.6    Die Klasse Thread erweitern Zur vorigen ÜberschriftZur nächsten Überschrift

Da die Klasse Thread selbst die Schnittstelle Runnable implementiert und die run()-Methode mit leerem Programmcode bereitstellt (siehe Unicode-Zeichenkodierung), können wir auch Thread erweitern, wenn wir eigene nebenläufige Aktivitäten programmieren wollen:

Listing 17.4     src/main/java/com/tutego/insel/thread/DateThread.java, DateThread

public class DateThread extends Thread {

@Override public void run() {

Stream.generate( LocalDateTime::now ).limit( 20 ).forEach( System.out::println );

}

}
UML-Diagramm der Klasse »Thread«, die »Runnable« implementiert

Abbildung 17.4     UML-Diagramm der Klasse »Thread«, die »Runnable« implementiert

Dann müssen wir kein Runnable-Exemplar mehr in den Konstruktor einfügen, denn wenn unsere Klasse eine Unterklasse von Thread ist, reicht ein Aufruf der geerbten Methode start(). Danach arbeitet das Programm direkt weiter, führt also kurze Zeit später die nächste Anweisung hinter start() aus:

Listing 17.5     src/main/java/com/tutego/insel/thread/DateThreadUser.java, main()

Thread t = new DateThread();

t.start();

new DateThread().start(); // (*)

Die (*)-Zeile zeigt, dass das Starten sehr kompakt auch ohne Zwischenspeicherung der Objektreferenz möglich ist.

class java.lang.Thread

implements Runnable
  • void run()

    Diese Methode in Thread hat einen leeren Rumpf. Unterklassen überschreiben run(), sodass sie den nebenläufig auszuführenden Programmcode enthält.

Überschreiben von start() und Selbststarter

Die Methode start() kann von uns auch überschrieben werden, was aber nur selten sinnvoll bzw. nötig ist. Wir müssen dann darauf achten, super.start() aufzurufen, damit der Thread wirklich startet. Damit wir als Thread-Benutzer nicht erst die start()-Methode aufrufen müssen, kann sich ein Thread auch selbst starten. Der Konstruktor ruft dazu einfach die eigene start()-Methode auf:

class DateThread extends Thread {

DateThread() {

start();

}

// ... der Rest bleibt ...

}

Statt start() wurde run() aufgerufen – ja, wo laufen sie denn?

run() ist wie start() eine öffentliche Methode der Klasse ThreadThread implementiert Runnable, daher muss die run()-Methode public sein. Ein Programmierfehler, der Anfängern schnell unterläuft, ist folgender: Statt start() rufen sie aus Versehen run() auf dem Thread auf. Was geschieht? Fast genau das Gleiche wie bei start(), aber mit dem Unterschied, dass die Objektmethode run() nichtnebenläufig zum übrigen Programm abgearbeitet wird. Der aktuelle Thread bearbeitet die run()-Methode sequenziell, bis sie zu Ende ist und die Anweisungen nach dem Aufruf an die Reihe kommen. Der Fehler fällt nicht immer direkt auf, denn die Aktionen in run() finden ja statt – nur eben nichtnebenläufig.

Erweitern von Thread oder Implementieren von Runnable?

Die beste Idee wäre, Runnable-Objekte zu bauen, die dann dem Thread übergeben werden. Befehlsobjekte dieser Art sind recht flexibel, da die einfachen Runnable-Objekte leicht übergeben und sogar von Threads aus einem Thread-Pool ausgeführt werden können. Ein Nachteil der Thread-Erweiterung ist, dass die Einfachvererbung störend sein kann; erbt eine Klasse von Thread, ist die Erweiterung schon »aufgebraucht«. Doch egal, ob eine Klasse Runnable implementiert oder Thread erweitert, eines bleibt: eine neue Klasse.

 


Ihre Meinung?

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de

<< zurück
 Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Java ist auch eine Insel Java ist auch eine Insel

Jetzt Buch bestellen


 Buchempfehlungen
Zum Rheinwerk-Shop: Captain CiaoCiao erobert Java

Captain CiaoCiao erobert Java




Zum Rheinwerk-Shop: Algorithmen in Java

Algorithmen in Java




Zum Rheinwerk-Shop: Spring Boot 3 und Spring Framework 6

Spring Boot 3 und Spring Framework 6




Zum Rheinwerk-Shop: Java SE 9 Standard-Bibliothek

Java SE 9 Standard-Bibliothek




 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und in die Schweiz

InfoInfo



 

 


Copyright © Rheinwerk Verlag GmbH 2024

Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.

Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

 

[Rheinwerk Computing]



Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de



Cookie-Einstellungen ändern