Musterlösungen
Laboraufgabe “Start eines Threads durch und bei Vererbung”
Die folgende Musterlösung gehört zum Projekt pp.01.01-Inheritance. Der vollständige Quellcode der Musterlösung ist im Projekt pp.01.01-Inheritance_solution:
- Der vorliegende Quellcode ist nur teilweise funktionsfähig. Probieren Sie die
main-Methoden vonSensorundThermometeraus.Sensorläuft,Thermometernicht.
- Analysieren Sie den Programmablauf.
- Der Konstruktor von
Thermometerruft zuerst den Konstruktor vonSensorauf. Am Ende desSensor-Konstruktors wirdstart()aufgerufen. DerThermometer-Thread startet also ohne, dass die Initialisierung beendet wurde:randist deshalb beim ersten Aufruf vonnextInt(...)null.
- Der Konstruktor von
- Korrigieren Sie das Programm, so dass beide Klassen lauffähig sind.
Sensor:start()von Konstruktor inmain-Methode verschieben.Thermometer:start()für beide Threads inmain-Methode aufnehmen.
- Verallgemeinern Sie Ihre “Findings” zu einem kurzen Merksatz!
- Niemals
start()in Konstruktor einer Threadsubklasse, denn das erschwert die Wiederverwendung durch Vererbung.
- Niemals
Laboraufgabe “Start eines Threads als Runnable und Refactoring”
Die folgende Musterlösung gehört zum Projekt pp.01.02-Runnable. Der vollständige Quellcode der Musterlösung ist im Projekt pp.01.02-Runnable_solution:
- Lassen Sie
Starterlaufen und analysieren Sie das Verhalten. Ändern Sie denStarterso, dass stattMyWorkerInstanzen vonMyWorkerCooperzeugt werden.
- Vergleichen Sie das Verhalten von beiden Varianten. Recherchieren Sie die API-Beschreibung von
Thread.yield()und erklären Sie sich den Unterschied. - Mit
yield()wird dem Scheduler signalisiert, dass beim Pseudo-Multitasking ein Wechsel zum nächsten wartenden Thread möglich ist. Deryield()aufrufende Thread gibt die Ressourcen zu diesem Punkt aktiv an den Scheduler zurück. Allerdings müssen JVM-Scheduler-Implementierungen dies nicht berücksichtigen (vgl. Java-API1). Falls doch, wird es dadurch öfter zu Kontextwechseln kommen. - Erzeugen Sie eine Kopie der Klasse
StarternamensStarterInner, die dieselbe Funktion hat wieStarteraber anstatt einer externen Klasse für den Worker eine anonyme innere Klasse verwendet.
package pp;
public class StarterInner {
static int WORKERS = 200;
public static void main(String... args) {
for (var i = 0; i < StarterInner.WORKERS; i++) {
var t = new Thread(new Runnable() {
private Thread self;
@Override
public void run() {
this.self = Thread.currentThread();
while (true) {
System.out.println(this.self.getName() + ": ID => " + this.self.threadId());
}
}
}, String.format("Worker-%03d", i));
t.start();
}
}
}- Erzeugen Sie eine Kopie der Klasse
StarternamensStarterLambda, die dieselbe Funktion hat wieStarteraber anstatt einer externen Klasse für den Worker einen Lambda-Ausdruck verwendet.- Die Instanzvariable
selfmuss zu einer lokalen Variable inrun()gemacht werden. Inhaltlich ist das in diesem Fall kein Problem.
- Die Instanzvariable
package pp;
public class StarterLambda {
static int WORKERS = 200;
public static void main(String... args) {
for (var i = 0; i < StarterLambda.WORKERS; i++) {
var t = new Thread(() -> {
var self = Thread.currentThread();
while (true) {
System.out.println(self.getName() + ": ID => " + self.threadId());
}
}, String.format("Worker-%03d", i));
t.start();
}
}
}Laboraufgabe “Lebenszyklus von Threads und Interrupts”
Analysieren Sie
Task. Wie lange wird mitThread.sleep()gewartet, bis es zurExceptionkommt?- 1000ms/10 + 1000ms/9 + 1000ms/8 + … 1000ms/1
- (100+111+125+143+167+200+250+333+500+1000)ms
- 2929ms
Analysieren Sie den Quellcode von
Runnerund lassen SieRunnerlaufen. Es sollte eine Exception auftreten. Wo wird sie behandelt?- im Rumpf des Lambda-Ausdrucks, der bei
UncaughtExceptionHandlerinRunnerübergeben wird. Alternativ wäre es an dieser Stelle auch möglich, eine anonyme innere Klasse zu instantiieren:
thread.setUncaughtExceptionHandler( new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { System.err.println("Unhandled Exception: " + e.getMessage()); System.err.println(" Thread: " + t.threadId() + " - " + t.getName()); System.err.println(" Thread State: " + t.getState()); e.printStackTrace(System.err); } });- im Rumpf des Lambda-Ausdrucks, der bei
Ändern Sie
Runnerso, dass nach dem Start vontaskein weiterer Thread erzeugt und gestartet wird, der dieTask-Instanztaskvon außen beendet.Tipp: Sparen Sie unnötige Tipparbeit und verwenden Sie einen Lambda-Ausdruck, um das Functional Interface (
run()) vonRunnablezu füllen, den Sie einfach einer neuenThread-Instanz übergeben können, die Sie sogleich starten (ein “Einzeiler”).Es sollte eine Exception auftreten. Wo wird sie behandelt?
- im
catch-Block in derrun()-Methode inTask:
- im
weil this.self.interrupt()) in stopRequest() aufgerufen wird, während Thread.sleep(1000 / i--) ausgeführt wird.
Warum ist es erforderlich in
stopRequest()vonTaskdem Thread vonTask, dessen Referenz inselfgespeichert ist, mitinterrupt()ein Signal zu schicken, ob wohl in die Hauptschleife vonrun()doch dadurch abgebrochen wird?- sonst wird ein lange laufendes
Thread.sleep(...)nicht unterbrochen, sondern zu Ende geführt.
- sonst wird ein lange laufendes
https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/lang/Thread.html#yield() (letzter Zugriff: 04.10.2024)↩︎