joeck / Concurrency

Java Concurrency

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

IT.PROG2 Praktikum Concurrency

Einleitung

Das folgende Praktikum umfasst einen relativ grossen Themenblock, der im Unterricht rund vier Wochen umfasst. Da das Praktikum startet, bevor sie den ganzen Stoff kennen, ist das Praktikum in die vier Vorlesungsthemen gegliedert. D.h. Sie können jeweils bis zu dem Teil lösen, der im Unterricht schon behandelt wurde.

Zudem gibt es in diesem Praktikum mehrere kleinere Pflichtaufgaben, mit [PA] gekennzeichnet sind.

Ziele

Ziele dieses Praktikums sind:

  • Sie verstehen die Grundlagen von Nebenläufigkeit

  • Sie können mehrere Java-Threads starten, kontrollieren und sauber beenden.

  • Sie können das Zustandsmodell von Threads erkären und wissen, welche Mechanismen den Wechsel der Zustände veranlassen.

  • Sie können die verschiedenen Mechanismen zur Thread-Synchronisation sicher anwenden (Mutual-Exclusion, Condition-Synchronisation).

  • Sie können das Monitor-Konzept (wait/notify, Lock & Conditions in Java anwenden.

  • Sie können Producer-Consumer Synchronisation praktisch anwenden.

  • Sie wissen was wie Deadlocks praktisch verhindert werden können.

  • Sie können die modernen Java-Hilfsmittel zum parallelen Ausführen von nebenläufigen Jobs praktisch anwenden.

Voraussetzungen

  • Vorlesung Concurrency 1 bis 4

Tooling

  • Installiertes JDK 11+

  • Gradle 6.1+

Struktur

Das Praktikum enthält verschiedene Arten von Aufgaben, die wie folgt gekennzeichnet sind:

[TU] – Theoretische Übung

Dient der Repetition bzw. Vertiefung des Stoffes aus der Vorlesung und als Vorbereitung für die nachfolgenden Übungen.

[PU] – Praktische Übung

Übungsaufgaben zur praktischen Vertiefung von Teilaspekten des behandelten Themas.

[PA] – Pflichtaufgabe

Übergreifende Aufgabe zum Abschluss. Das Lösen dieser Aufgaben ist Pflicht. Sie muss bis zum definierten Zeitpunkt abgegeben werden, wird bewertet und ist Teil der Vornote.

Zeit und Bewertung

Für das Praktikum stehen 3 Wochen in den Praktikumslektionen und im Selbststudium zur Verfügung.
Je nach Kenntniss- und Erfahrungsstufe benötigen Sie mehr oder weniger Zeit. Nutzen Sie die Gelegenheit den Stoff und zu vertiefen, Auszuprobieren, Fragen zu stellen und Lösungen zu diskutieren (Intensive-Track).
Falls Sie das Thema schon beherrschen müssen Sie nur die Pflichtaufgaben lösen und bis zum angegebenen Zeitpunkt abgeben (Fast-Track).

Die Pflichtaufgabe wird mit 0 bis 2 Punkten bewertet (siehe Leistungsnachweise auf Moodle).

1. Concurrency 1 — Java Threads

1.1. Theoretische Fragen [TU]

  1. Im Unterricht haben Sie zwei Varianten kennengelernt um Threads zu erzeugen. Erläutern Sie jeweils, was für die Implementation spezifisch ist und wie die Thread-Instanz erzeugt und gestartet wird.

  2. Erläutern Sie im nachfolgenden (vereinfachten) Thread-Zustandsmodell, was die aufgeführten Zustände bedeuten und ergänzen Sie die Mechanismen welche den Wechsel zwischen den Zuständen auslösen. Wenn vorhanden, geben Sie den entsprechenden Befehl an.

    Thread State Model
    Figure 1. Thread Zustandsmodell (vereinfacht)

1.2. Printer-Threads: Verwendung von Java Threads [PU]

Nachfolgend einige Basisübungen zum Starten und Stoppen von Threads in Java.

public class Printer {

    // test program
    public static void main(String[] arg) {
        PrinterThread a = new PrinterThread("PrinterA", '.', 10);
        PrinterThread b = new PrinterThread("PrinterB", '*', 20);
        a.start();
        b.start();
        b.run(); // wie kann das abgefangen werden?
    }


    private static class PrinterThread extends Thread {
        char symbol;
        int sleepTime;

        public PrinterThread(String name, char symbol, int sleepTime) {
            super(name);
            this.symbol = symbol;
            this.sleepTime = sleepTime;
        }

        public void run() {
            System.out.println(getName() + " run started...");
            for (int i = 1; i < 100; i++) {
                System.out.print(symbol);
                try {
                    Thread.sleep(sleepTime);
                } catch (InterruptedException e) {
                    System.out.println(e.getMessage());
                }
            }
            System.out.println('\n' + getName() + " run ended.");
        }
    }
}
  1. Studieren Sie das Programm Printer.java: Die Methode Thread.run() ist public und kann daher direkt aufgerufen werden. Erweitern Sie die Methode run() so, dass diese sofort terminiert, wenn sie direkt und nicht vom Thread aufgerufen wird.


    💡
    Was liefert die Methode Thread.currentThread() zurück?
  2. Erstellen sie eine Kopie von Printer.java (z.B. PrinterB.java) und schreiben Sie das Programm so um, dass die run-Methode über das Interface Runnable implementiert wird.

    Führen Sie dazu eine Klasse PrinterRunnable ein, die das Interface Runnable implementiert.
    
Starten Sie zwei Threads, so dass die selbe Ausgabe entsteht wie bei (a).

  3. Wie kann erreicht werden, dass die Fairness erhöht wird, d.h. dass der Wechsel zwischen den Threads häufiger erfolgt? Wirkt es sich aufs Resultat aus?

  4. Wie muss man das Hauptprogramm anpassen, damit der Main-Thread immer als letztes endet?

1.3. Thread Priority [PU]

In dieser Aufgabe üben Sie als erstes wie ein endloss laufender Thread correkt von aussen beendet wird. Zudem vermittelt diese Aufgabe vermittelt einen Einblick in das Prioritätensystem der Java Threads. Als Basis der Aufgabe dient die Klasse PriorityTest.java.

  1. Als Erstes, ersetzen Sie die veraltete Methode thread.stop() durch eine "saubere" Variante die SimpleThread-Instanzen zu beenden. Dabei sollen sichergestellt werden, dass auch Threads beendet werden, die momentan gerade "pausiert" sind.

  2. Mit Hilfe der Klasse soll das Verhalten von Java Threads mit verschiedenen Prioritäten analysiert werden.

    💡
    Es kann sein, dass verschiedene Betriebssysteme und Java-Versionen sich unterschiedlich verhalten http://www.javamex.com/tutorials/threads/priority.shtml

    Je nach Priorität im Bereich von Thread.MIN_PRIORITY=1 über Thread.NORM_PRIORITY=5 bis Thread.MAX_PRIORITY=10, sollte der Thread vom Scheduler bevorzugt behandelt werden, d.h. der Zähler count sollte häufiger inkrementiert werden.
    Kommentieren Sie dazu den Thread.sleep() try-catch-Block aus, um sicherzusstellen, dass damit der Thread nicht zum Freigeben der CPU gezwungen wird.

    Folgende Fragen müssen abgeklärt und beantwortet werden:

    • Wie verhält es sich, wenn alle Threads die gleiche Priorität haben?

    • Was stellen Sie fest, wenn die Threads unterschiedliche Priorität haben?
      Erhöhen Sie auch die Anzahl Threads (z.B. 100), um eine Ressourcen-Knappheit zu provozieren.

2. Concurrency 2 — Thread Synchronisation

2.1. Konto-Übertrag [PU]

Nachfolgend eine einfache Klasse, um ein Konto zu verwalten, den Saldo abzufragen oder zu aktualisieren.

public class Account {
    private int id;
    private int saldo = 0;

    public Account(int id, int initialAmount) {
        this.id = id;
        this.saldo = initialAmount;
    }

    public int getId() {
        return id;
    }

    public int getSaldo() {
        return saldo;
    }

    public void changeSaldo(int delta) {
        this.saldo += delta;
    }
}

Ein Entwickler implementiert aufbauend auf der Klasse Account eine Operation für den Transfer eines Geldbetrages zwischen zwei Konti. Die Klasse AccountTransferThread implementiert dazu die Methode accountTransfer, welche in einer Schleife mehrfach aufgerufen wird, um viele kleine Transaktionen zu simulieren. Das Testprogramm AccountTransferTest (siehe abgegebenen Code) erzeugt schlussendlich mehrere Threads, die teilweise auf denselben Konto-Objekten operieren.

class AccountTransferThread extends Thread {

    private Account fromAccount;
    private Account toAccount;
    private int amount;
    private int maxIter = 10000;

    public AccountTransferThread(String name, Account fromAccount,
                                 Account toAccount, int amount)
    {
        super(name);
        this.fromAccount = fromAccount;
        this.toAccount = toAccount;
        this.amount = amount;
    }

    /*  Transfer amount from fromAccount to toAccount */
    public void accountTransfer() {
        // Account must not be overdrawn
        if (fromAccount.getSaldo() >= amount) {
            fromAccount.changeSaldo(-amount);
            toAccount.changeSaldo(amount);
        }
    }

    public void run() {
        for (int i = 0; i < maxIter; i++) {
            accountTransfer();
            try { // simulation of work time
                Thread.sleep((int) (Math.random() * 10));
            } catch (InterruptedException e) {
                System.out.println(e.getMessage());
            }
        }
        System.out.println("DONE! " + getName());
    }
}
  1. Was stellen Sie fest, wenn Sie das Testprogramm laufen lassen?
 Erklären Sie wie die Abweichungen zustande kommen.

  2. Im Unterricht haben Sie gelernt, dass sie kritische Bereiche Ihres Codes durch Mutual-Exclusion geschützt werden sollen. Wie macht man das in Java?

    Versuchen Sie mit Hilfe von Mutual-Exclusion sicher zu stellen, dass keine Abweichungen entstehen. Reicht es, wenn Sie die kritischen Methoden in Account schützen?

    Untersuchen Sie mehrere Varianten von Locks (Lock auf Methode oder Block, Lock auf Instanz oder Klasse).

    Ihre Implementierung muss noch nebenläufige Transaktionen erlauben, d.h. wenn Sie zu stark synchronisieren, werden alle Transaktionen in Serie ausgeführt und Threads machen keinen Sinn mehr.
    
Stellen Sie für sich folgende Fragen:

    • Welches ist das Monitor-Objekt?

    • Braucht es eventuell das Lock von mehr als einen Monitor während der Transaktion?

  3. Wenn Sie es geschafft haben die Transaktion thread-safe zu implementieren, ersetzen Sie in AccountTransferTest die die folgende Zeile
:
    AccountTransferThread t1 =
new AccountTransferThread("Worker 1", account3, account1, 1);

    durch
    
AccountTransferThread t1 =
new AccountTransferThread("Worker 1", account1, account3, 1);
    
und starten Sie das Programm noch einmal. Was stellen Sie fest? (evtl. müssen Sie es mehrfach versuchen, damit der Effekt auftritt).
    Was könnte die Ursache sein und wie können Sie es beheben?

    ℹ️
    Falls Sie die Frage noch nicht beantworten können, kommen sie nach der Vorlesung "Concurrency 3" nochmals auf diese Aufgabe zurück und versuchen Sie sie dann zu lösen.

2.2. Traffic Light [PU]

In dieser Aufgabe sollen Sie die Funktionsweise einer Ampel und deren Nutzung nachahmen. Benutzen Sie hierzu die Vorgabe TrafficLightOperation.java.

  1. Ergänzen Sie zunächst in der Klasse TrafficLight drei Methoden:

    • Eine Methode zum Setzen der Ampel auf „rot“.

    • Eine Methode zum Setzen der Ampel auf „grün“.

    • Eine Methode mit dem Namen passby(). Diese Methode soll das Vorbeifahren eines Fahrzeugs an dieser Ampel nachbilden: Ist die Ampel rot, so wird der aufrufende Thread angehalten, und zwar so lange, bis die Ampel grün wird. Ist die Ampel dagegen grün, so kann der Thread sofort aus der Methode zurückkehren, ohne den Zustand der Ampel zu verändern. Verwenden Sie wait, notify und notifyAll nur an den unbedingt nötigen Stellen!

      ℹ️
      Die Zwischenphase „gelb“ spielt keine Rolle – Sie können diesem Zustand ignorieren!
  2. Erweitern Sie nun die Klasse Car (abgeleitet von Thread). Im Konstruktor wird eine Referenz auf ein Feld von Ampeln übergeben. Diese Referenz wird in einem entsprechenden Attribut der Klasse Car gespeichert. In der run-Methode werden alle Ampeln dieses Feldes passiert, und zwar in einer Endlosschleife (d.h. nach dem Passieren der letzten Ampel des Feldes wird wieder die erste Ampel im Feld passiert).
    Natürlich darf das Auto erst dann eine Ampel passieren, wenn diese auf grün ist!
    Für die Simulation der Zeitspanne für das Passieren der des Signals können Sie die folgende Anweisung verwenden: sleep((int)(Math.random() * 500));

Beantworten Sie entweder (c) oder (d) (nicht beide):

  1. Falls Sie bei der Implementierung der Klasse TrafficLight die Methode notifyAll() benutzt haben: Hätten Sie statt notifyAll auch die Methode notify verwenden können, oder haben Sie notifyAll() unbedingt gebraucht? 
Begründen Sie Ihre Antwort!

  2. Falls Sie bei der Implementierung der Klasse Ampel die Methode notify() benutzt haben: Begründen Sie, warum Sie notifyAll() nicht unbedingt gebraucht haben!

  3. Testen Sie das Programm TrafficLightOperation.java. Die vorgegebene Klasse implementiert eine primitive Simulation von Autos, welche die Ampeln passieren. Studieren Sie den Code dieser Klasse und überprüfen Sie, ob die erzeugte Ausgabe sinnvoll ist.

2.3. Producer-Consumer Problem [PA]

In dieser Aufgabe wird ein Producer-Consumer Beispiel mit Hilfe einer Queue umgesetzt.

Dazu wird eine Implementation mittels eines Digitalen Ringspeichers umgesetzt.

Circular Buffer Animation
Figure 2. Circular Buffer [Wikipedia]

Hierzu sind zwei Klassen (CircularBuffer.java, Buffer.java) vorgegeben, mit folgendem Design:

CircularBuffer
Figure 3. Circular Buffer Design

Erstellen von Producer- und Consumer-Threads

Als erstes soll ein Producer und ein Consumer implementiert werden. Nachfolgend ist das Gerüst für beide Klassen abgebildet (siehe CircBufferTest.java):

class Producer extends Thread {
    public Producer(String name, Buffer buffer, int prodTime) {
        // ...
    }
    public void run() {
        // ...
    }
}

class Consumer extends Thread
{
    public Consumer(String name, Buffer buffer, int consTime) {
        // ...
    }
    public void run() {
        // ...
    }
}

Der Producer soll Daten in den Buffer einfüllen, und der Consumer soll Daten auslesen. Auf den Buffer soll nur über das Interface zugegriffen werden. Das Zeitintervall, der ein Producer braucht um die Daten zu erstellen, ist mit sleep((int)(Math.random()*prodTime)) zu definieren. Die Zeit für verarbeitung des Consumers soll entsprechend mit sleep((int)(Math.random() * consTime)) bestimmt werden.

Für Producer und Consumer wurde bereits ein Testprogramm (CircBufferTest) geschrieben. Testen Sie damit ihre Consumer- und Producer-Klassen. Versuchen sie den generierten Output auf der Console richtig zu interpretieren! Spielen sie mit den Zeitintervallbereichen von Producer (maxProdTime) und Consumer (maxConsTime) und ziehen sie Schlüsse. Erstellen sie über die Modifikation von prodCount und consCount mehrere Producer bzw. Consumer.

ℹ️

Generieren sie in den selber implementierten Klassen keine eigene Ausgabe. Ändern sie den bestehenden Code nicht. Es stehen zwei Ausgabefunktionen zur Auswahl: printBufferContent() und printBufferSlots().

Thread-Safe Circular Buffer

In der vorangehenden Übung griffen mehrere Threads auf den gleichen Buffer zu. Die Klasse CircularBuffer ist aber nicht thread-safe. Was wir gemacht haben, ist daher nicht tragbar. Deshalb soll jetzt eine Wrapper Klasse geschrieben werden, welche die CircularBuffer-Klasse "thread-safe" macht. Das führt zu folgendem Design:

GuardedCircularBuffer
Figure 4. Guarded Circular Buffer Desing

Aufrufe von put blockieren, solange der Puffer voll ist, d.h., bis also mindestens wieder ein leeres Puffer-Element vorhanden ist. Analog dazu blockieren Aufrufe von get, solange der Puffer leer ist, d.h, bis also mindestens ein Element im Puffer vorhanden ist.

💡

Verwenden Sie den Java Monitor des GuardedCircularBuffer-Objektes! Wenn die Klasse fertig implementiert ist, soll sie in der CircBufferTest Klasse verwendet werden.

Beantworten Sie entweder (a) oder (b) (nicht beide):

  1. Falls Sie bei der Implementierung der Klasse GuardedCircularBuffer die Methode notifyAll() benutzt haben: Hätten Sie statt notifyAll() auch die Methode notify() verwenden können oder haben Sie notifyAll() unbedingt gebraucht? Begründen Sie Ihre Antwort!

  2. Falls Sie bei der Implementierung der Klasse GuardedCircularBuffer die Methode notify() benutzt haben: Begründen Sie, warum Sie notifyAll() nicht unbedingt gebraucht haben!

3. Concurrency 3 — Lock & Conditions, Deadlocks

3.1. Single-Lane Bridge [PU]

Die Brücke über einen Fluss ist so schmal, dass Fahrzeuge nicht kreuzen können. Sie soll jedoch von beiden Seiten überquert werden können. Es braucht somit eine Synchronisation, damit die Fahrzeuge nicht kollidieren. Um das Problem zu illustrieren wird eine fehlerhaft funktionierende Anwendung, in welcher keine Synchronisierung vorgenommen wird, zur Verfügung gestellt. Ihre Aufgabe ist es die Synchronisation der Fahrzeuge einzubauen.

Die Anwendung finden Sie im Ordner handout/Bridge. Nach dem Kompilieren (z.B. mit gradle build) können Sie diese starten, in dem Sie die Klasse Main ausführen (z.B. mit gradle run). Das GUI sollte selbsterklärend sein. Mit den zwei Buttons können sie Autos links bzw. rechts hinzufügen. Sie werden feststellen, dass die Autos auf der Brücke kollidieren.

bridge overview
Figure 5. Single-Lane Bridge GUI

Um das Problem zu lösen müssen Sie die den GUI Teil der Anwendung nicht verstehen. Sie müssen nur wissen, dass Fahrzeuge die von links nach rechts fahren die Methode controller.enterLeft() aufrufen bevor sie auf die Brücke fahren (um Erlaubnis fragen) und die Methode controller.leaveRight() aufrufen sobald sie die Brücke verlassen. Fahrzeuge in die andere Richtung rufen entsprechend die Methoden enterRight() und leaveLeft() auf. Dabei ist controller eine Instanz der Klasse TrafficController welche für die Synchronisation zuständig ist. In der mitgelieferte Klasse sind die vier Methoden nicht implementiert (Dummy-Methoden).

  1. Bauen sie die Klasse TrafficController in einen Monitor um der sicherstellt, dass die Autos nicht mehr kollidieren. Verwenden Sie dazu den Lock und Conditions Mechanismus.

    💡
    Verwenden Sie eine Statusvariable um den Zustand der Brücke zu repräsentieren (e.g. boolean bridgeOccupied).
  2. Erweitern Sie die Klasse TrafficController so, dass jeweils abwechslungsweise ein Fahrzeug von links und rechts die Brücke passieren kann. Unter Umständen wird ein Auto blockiert, wenn auf der anderen Seite keines mehr wartet. Verwenden Sie für die Lösung mehrere Condition Objekte.

  3. Bauen Sie die Klasse TrafficController um, so dass jeweils alle wartenden Fahrzeuge aus einer Richtung passieren können und erst wenn keines mehr wartet die Gegenrichtung fahren kann.

    💡
    Mit ReentrentLock.hasWaiters(Condition c) können Sie abfragen ob Threads auf eine bestimmte Condition warten.

3.2. The Dining Philosophers [PA]

Beschreibung des Philosophen-Problems:

Fünf Philosophen sitzen an einem Tisch mit einer Schüssel, die immer genügend Spaghetti enthält. Ein Philosoph ist entweder am Denken oder am Essen. Um zu essen braucht er zwei Gabeln. Es hat aber nur fünf Gabeln. Ein Philosoph kann zum Essen nur die neben ihm liegenden Gabeln gebrauchen. Aus diesen Gründen muss ein Philosoph warten und hungern, solange einer seiner Nachbarn am Essen ist.

philosopher table numbered
Figure 6. Philosopher Table
philosopher ui
Figure 7. Philosopher UI

Das zweite Bild zeigt die Ausgabe des Systems, das wir in dieser Aufgabe verwenden. Die schwarzen Kreise stellen denkende Philosophen dar, die gelben essende und die roten hungernde. Bitte beachten Sie, dass eine Gabel, die im Besitz eines Philosophen ist, zu dessen Teller hin verschoben dargestellt ist.

  1. Analysieren Sie die bestehende Lösung (PhilosopherTable.java), die bekanntlich nicht Deadlock-frei ist. Kompilieren und starten Sie die Anwendung. Nach einiger Zeit geraten die Philosophen in eine Deadlock-Situation und verhungern. Überlegen Sie sich, wo im Code der Deadlock entsteht und versuchen Sie, dessen Auftreten schneller herbeizuführen.

  2. Passen Sie die bestehende Lösung so an, dass keine Deadlocks mehr möglich sind. Passen Sie den ForkManager so an, dass sich Gabelpaare in einer atomaren Operation belegen bzw. freigegeben lassen. Die GUI-Klasse müssen Sie nicht anpassen. Die Änderungen an der Klasse Philosoph sind minimal, da sie nur den Methodenaufruf für die Freigabe bzw. Belegung der Gabeln ändern müssen.

    ℹ️
    Verwenden Sie für die Synchronisation Locks und Conditions!
    Testen Sie ihre Lösung auf Deadlock-Freiheit!
  3. In der Vorlesung haben Sie mehrere Lösungsansätze kennen gelernt. Erläutern Sie (theoretisch) wie implementiert werden könnte, wenn Sie den Deadlock über Nummerierung der Ressourcen verhindern möchten.

4. Concurrency 4 — Executor Framework, Callables and Futures

4.1. Theoretische Fragen [TU]

Im Unterricht haben sie verschieden Arten von Thread-Pools kennengelernt. Welcher davon würde sich für die folgend Anwendungsfälle am Besten eignen?
Wenn nötig, geben Sie auch die Konfiguration des Thread-Pools an.

  1. Sie schreiben einen Server, der via Netzwerk Anfragen erhält. Jede Anfrage soll in einem eigenen Task beantwortet werden. Die Anzahl gleichzeitiger Anfragen schwankt über den Tag verteilt stark.

  2. Ihr Graphikprogramm verwendet komplexe Mathematik um von hunderten von Objekten die Position, Geschwindigkeit und scheinbare Grösse (aus Sicht des Betrachters) zu berechnen und auf dem Bildschirm darzustellen.

  3. Je nach Datenset sind unterschiedliche Algorithmen schneller in der Berechnung des Resultats (z.B. Sortierung). Sie möchten jedoch in jedem Fall immer so schnell wie möglich das Resultat haben und lassen deshalb mehrere Algorithmen parallel arbeiten.

4.2. Mandelbrot

Die JavaFX-Anwendung Mandelbrot berechnet die Fraktaldarstellung eines Ausschnitts aus der Mandelbrot-Menge. Dazu wird die zeilenweise Berechnung auf mehrere Threads aufgeteilt.

ℹ️
Sie müssen die Mathematik hinter den Mandelbrotfraktalen nicht verstehen um die Aufgaben zu lösen.

In der abgegebenen Version werden die Threads noch "konventionell" erzeugt und beendet. Der Benutzer kann wählen, wieviele Threads verwendet werden sollen. Jedem Thread wird dann ein Block von Zeilen zugeteilt (startRow…​endRow), welcher diese berechnet und ausgibt.

Analysieren und testen Sie die Anwendung:

  1. Mit welcher Anzahl Threads erhalten Sie auf Ihrem Rechner die besten Resultate?

    💡
    Die Gesamtrechenzeit wird in der Konsole ausgegeben.
  2. Wie interpretieren Sie das Resultat im Verhältnis zur Anzahl Cores ihres Rechners?

4.2.1. Mandelbrot Executor [PA]

Erstellen Sie eine Kopie der Klasse Mandelbrot mit Namen MandelbrotExecutor.

Bauen Sie die Anwendung um, so dass für das Management der Tasks ein ExecutorService mit einem Thread-Pool verwendet wird.

Hinweise:
  • Die Methoden start, startOrStopCalculation, taskFinished und drawOneRow gehören zum GUI und sollten nicht verändert werden müssen.

  • Das Task-Management sollte durch Anpassen der Methoden startTasks, stopTasks und der Klasse MandelbrotTask erfolgen.

  • Überlegen Sie sich, welchen Typ von Thread-Pool hier sinnvollerweise verwendet wird.

  • Verwenden Sie den vom Benutzer gesetzten ThreadCount um die Grösse des Thread-Pools zu definieren.

  • Neu soll der MandelbrotTask nur noch eine Zeile berechnen und ausgeben. Das heisst, es muss für die Berechnung jeder Zeile ein Task erzeugt und übermittelt werden, nicht mehr pro Zeilenblock.

4.2.2. Mandelbrot Callable [PU]

Erstellen Sie eine weitere Kopie mit dem Namen MandelbrotCallable.

Bauen Sie die Anwendung so um, dass MandelbrotTask ein Callable ist, welches eine Zeile berechnet und zurückgibt.

Hinweise:
  • Wie in der vorherigen Aufgabe soll ein Task pro Zeile verwendet werden.

  • Die Ausgabe der Zeile erfolgt jetzt nach Abschluss der Berechnung im GUI-Thread.

  • Überlegen Sie sich welche Optionen Sie haben, um auf die Resultate zu warten und sicherzustellen, dass alle ausgegeben wurden.

Abschluss

Stellen Sie sicher, dass die Pflichtaufgaben mittels gradle run gestartet werden können und pushen Sie die Lösung vor der Deadline in ihr Abgaberepository.

About

Java Concurrency


Languages

Language:Java 100.0%