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 von Sensor und Thermometer aus.
    • Sensor läuft, Thermometer nicht.
  • Analysieren Sie den Programmablauf.
    • Der Konstruktor von Thermometer ruft zuerst den Konstruktor von Sensor auf. Am Ende des Sensor-Konstruktors wird start() aufgerufen. Der Thermometer-Thread startet also ohne, dass die Initialisierung beendet wurde: rand ist deshalb beim ersten Aufruf von nextInt(...) null.
  • Korrigieren Sie das Programm, so dass beide Klassen lauffähig sind.
    • Sensor: start() von Konstruktor in main-Methode verschieben.
    • Thermometer: start() für beide Threads in main-Methode aufnehmen.
  • Verallgemeinern Sie Ihre “Findings” zu einem kurzen Merksatz!
    • Niemals start() in Konstruktor einer Threadsubklasse, denn das erschwert die Wiederverwendung durch Vererbung.

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 Starter laufen und analysieren Sie das Verhalten. Ändern Sie den Starter so, dass statt MyWorker Instanzen von MyWorkerCoop erzeugt werden.
public static void main(String... args) {
    for (var i = 0; i < Starter.WORKERS; i++) {
        var t = new Thread(new MyWorkerCoop(), String.format("Worker-%03d", i));
        t.start();
    }
}
  • 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. Der yield() 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 Starter namens StarterInner, die dieselbe Funktion hat wie Starter aber 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 Starter namens StarterLambda, die dieselbe Funktion hat wie Starter aber anstatt einer externen Klasse für den Worker einen Lambda-Ausdruck verwendet.
    • Die Instanzvariable self muss zu einer lokalen Variable in run() gemacht werden. Inhaltlich ist das in diesem Fall kein Problem.
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 mit Thread.sleep() gewartet, bis es zur Exception kommt?

    • 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 Runner und lassen Sie Runner laufen. Es sollte eine Exception auftreten. Wo wird sie behandelt?

    • im Rumpf des Lambda-Ausdrucks, der bei UncaughtExceptionHandler in Runner ü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);
          }
      });
  • Ändern Sie Runner so, dass nach dem Start von task ein weiterer Thread erzeugt und gestartet wird, der die Task-Instanz task von außen beendet.

    Tipp: Sparen Sie unnötige Tipparbeit und verwenden Sie einen Lambda-Ausdruck, um das Functional Interface (run()) von Runnable zu füllen, den Sie einfach einer neuen Thread-Instanz übergeben können, die Sie sogleich starten (ein “Einzeiler”).

      // falsch: thread läuft weiter:
    // (new Thread(() -> thread.interrupt())).start();
    
    // richtig: thread wird beendet:
    (new Thread(() -> task.stopRequest())).start();
  • Es sollte eine Exception auftreten. Wo wird sie behandelt?

    • im catch-Block in der run()-Methode in Task:
try {
  Thread.sleep(1000 / i--);
} catch (InterruptedException e) {
  Thread.currentThread().interrupt();
}

weil this.self.interrupt()) in stopRequest() aufgerufen wird, während Thread.sleep(1000 / i--) ausgeführt wird.

  • Warum ist es erforderlich in stopRequest() von Task dem Thread von Task, dessen Referenz in self gespeichert ist, mit interrupt() ein Signal zu schicken, ob wohl in die Hauptschleife von run() doch dadurch abgebrochen wird?

    • sonst wird ein lange laufendes Thread.sleep(...) nicht unterbrochen, sondern zu Ende geführt.

  1. https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/lang/Thread.html#yield() (letzter Zugriff: 04.10.2024)↩︎