Initial commit
This commit is contained in:
commit
8ca02de8bc
|
@ -0,0 +1,29 @@
|
||||||
|
# EditorConfig is awesome: https://EditorConfig.org
|
||||||
|
|
||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# Default formatting Unix-style newlines with a newline ending every file
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
# do not trim trailing whitespace in markdown files
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
# explicit 4 space indentation
|
||||||
|
[*.py]
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
# explicit 2 space indentation
|
||||||
|
[*.{json, yml, yaml, xml, ddl, sql}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
# windows specific files
|
||||||
|
[*.{bat, cmd}]
|
||||||
|
end_of_line = crlf
|
|
@ -0,0 +1,5 @@
|
||||||
|
#
|
||||||
|
# https://help.github.com/articles/dealing-with-line-endings/
|
||||||
|
#
|
||||||
|
# These are explicitly windows files and should use crlf
|
||||||
|
*.bat text eol=crlf
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"name": "Run PA unit tests",
|
||||||
|
"setup": "",
|
||||||
|
"run": "gradle :Mandelbrot:test --info",
|
||||||
|
"input": "",
|
||||||
|
"output": "",
|
||||||
|
"comparison": "included",
|
||||||
|
"timeout": 10,
|
||||||
|
"points": 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
name: GitHub Classroom Workflow
|
||||||
|
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Autograding
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up JDK 17
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: '17'
|
||||||
|
- uses: education/autograding@v1
|
|
@ -0,0 +1,70 @@
|
||||||
|
# InelliJ IDEA files
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.ids
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Eclipse files
|
||||||
|
.project
|
||||||
|
.metadata
|
||||||
|
.classpath
|
||||||
|
.settings/
|
||||||
|
.loadpath
|
||||||
|
bin/
|
||||||
|
|
||||||
|
# Netbeans
|
||||||
|
nbactions.xml
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
# Maven
|
||||||
|
target/
|
||||||
|
|
||||||
|
# gradle files
|
||||||
|
.gradle
|
||||||
|
build/
|
||||||
|
|
||||||
|
# ignore logfiles
|
||||||
|
*.log*
|
||||||
|
|
||||||
|
# OS dependant files
|
||||||
|
.DS_Store
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
Thumbs.db
|
||||||
|
Desktop.ini
|
||||||
|
*~
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# compiled files
|
||||||
|
*.com
|
||||||
|
*.class
|
||||||
|
*.dll
|
||||||
|
*.exe
|
||||||
|
*.o
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# packages
|
||||||
|
*.7z
|
||||||
|
#*.jar
|
||||||
|
*.rar
|
||||||
|
*.zip
|
||||||
|
*.gz
|
||||||
|
*.bzip
|
||||||
|
*.xz
|
||||||
|
*.lzma
|
||||||
|
*~$*
|
||||||
|
|
||||||
|
# package managment formats
|
||||||
|
*.dmg
|
||||||
|
*.xpi
|
||||||
|
*.gem
|
||||||
|
*.egg
|
||||||
|
*.deb
|
||||||
|
*.rpm
|
||||||
|
|
||||||
|
# databases
|
||||||
|
*.sqlite
|
|
@ -0,0 +1,263 @@
|
||||||
|
:source-highlighter: coderay
|
||||||
|
:icons: font
|
||||||
|
:experimental:
|
||||||
|
:!sectnums:
|
||||||
|
:imagesdir: ./images/
|
||||||
|
:handout: ./code/
|
||||||
|
|
||||||
|
:logo: IT.PROG2 -
|
||||||
|
ifdef::backend-html5[]
|
||||||
|
:logo: image:PROG2-300x300.png[IT.PROG2,100,100,role=right,fit=none,position=top right]
|
||||||
|
endif::[]
|
||||||
|
ifdef::backend-pdf[]
|
||||||
|
:logo:
|
||||||
|
endif::[]
|
||||||
|
ifdef::env-github[]
|
||||||
|
:tip-caption: :bulb:
|
||||||
|
:note-caption: :information_source:
|
||||||
|
:important-caption: :heavy_exclamation_mark:
|
||||||
|
:caution-caption: :fire:
|
||||||
|
:warning-caption: :warning:
|
||||||
|
endif::[]
|
||||||
|
|
||||||
|
= {logo} Praktikum Concurrency - Execution
|
||||||
|
|
||||||
|
== Einleitung
|
||||||
|
|
||||||
|
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 das Java Executor Framework zum Ausführen von nebenläufigen Tasks praktisch anwenden.
|
||||||
|
|
||||||
|
|
||||||
|
=== Voraussetzungen
|
||||||
|
* Vorlesung Concurrency - Execution 1 und 2
|
||||||
|
|
||||||
|
=== Tooling
|
||||||
|
|
||||||
|
* Installiertes JDK 17+
|
||||||
|
* Gradle 7.3+
|
||||||
|
|
||||||
|
=== Struktur
|
||||||
|
|
||||||
|
Ein Praktikum kann verschiedene Arten von Aufgaben enthalten, 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 dieses Praktikum stehen 2 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).
|
||||||
|
|
||||||
|
=== Referenzen
|
||||||
|
|
||||||
|
* link:{handout}[Praktkikumsverzeichnis – Quellcode, Projektstruktur]
|
||||||
|
|
||||||
|
:sectnums:
|
||||||
|
:sectnumlevels: 2
|
||||||
|
// Beginn des Aufgabenblocks
|
||||||
|
|
||||||
|
== Concurrency 1 -- Java Threads
|
||||||
|
|
||||||
|
=== Theoretische Fragen [TU]
|
||||||
|
|
||||||
|
[loweralpha]
|
||||||
|
. 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.
|
||||||
|
. 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 Zustandsmodell (vereinfacht)
|
||||||
|
image::Thread-State-Model.png[pdfwidth=80%, width=900px]
|
||||||
|
|
||||||
|
=== Printer-Threads: Verwendung von Java Threads [PU]
|
||||||
|
|
||||||
|
Nachfolgend einige Basisübungen zum Starten und Stoppen von Threads in Java.
|
||||||
|
|
||||||
|
[source, 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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
[loweralpha]
|
||||||
|
. 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.
|
||||||
|
+
|
||||||
|
[TIP]
|
||||||
|
Was liefert die Methode `Thread.currentThread()` zurück?
|
||||||
|
|
||||||
|
. 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).
|
||||||
|
. 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?
|
||||||
|
. Wie muss man das Hauptprogramm anpassen, damit der Main-Thread immer als letztes endet?
|
||||||
|
|
||||||
|
|
||||||
|
== Concurrency 2 -- Executor Framework, Callables and Futures
|
||||||
|
|
||||||
|
=== 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.
|
||||||
|
|
||||||
|
[loweralpha]
|
||||||
|
. 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.
|
||||||
|
. 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.
|
||||||
|
. 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.
|
||||||
|
|
||||||
|
=== Prime Checker [PU]
|
||||||
|
|
||||||
|
In dieser Aufgabe üben sie die Verwendung des Java Executor Frameworks zum Ausführen von mehreren unabhängigen Aufgaben (Tasks).
|
||||||
|
Mit der Wahl des Typs und der Konfiguration des ExecutorServices, bestimmen Sie auch ob und wie diese Tasks parallel d.h. in Threads ablaufen.
|
||||||
|
|
||||||
|
Im link:{{handout}[Praktikumsverzeichnis] finden sie das Modul `PrimeChecker`.
|
||||||
|
Die Anwendung testet für eine Menge an zufälligen grossen Zahlen, ob es sich dabei um eine Primzahl handelt, indem es Brute-Force nach dem kleinstmöglichen Faktor (>1) sucht, durch den die Zahl ganzzahlig geteilt werden kann.
|
||||||
|
|
||||||
|
Die Klasse 'PrimeChecker' enthält die Hauptanwendung, welche in einer Schleife zufällige Zahlen erzeugt und testet.
|
||||||
|
Die Verifizierung, ob es sich um eine Primzahl handelt, ist in die Klasse `PrimeTask` ausgelagert, welche bereits `Runnable` implementiert.
|
||||||
|
In der ausgelieferten Form wird jedoch alles im `main`-Thread ausgeführt.
|
||||||
|
|
||||||
|
[loweralpha]
|
||||||
|
. Studieren und testen Sie `PrimeChecker`. +
|
||||||
|
Wie lange dauert die Analyse der Zahlen aktuell?
|
||||||
|
. Erweitern Sie `PrimeChecker` damit für jede Analyse (`PrimeTask`-Instanz) mit `new` ein eigener Thread gestartet wird. +
|
||||||
|
[arabic]
|
||||||
|
.. Wie lange dauert die Analyse jetzt?
|
||||||
|
.. Wie viele Threads werden gestartet?
|
||||||
|
|
||||||
|
Im nächsten Schritt soll für das Ausführen der `PrimeTask`-Instanzen ein ExecutorService verwendet werden.
|
||||||
|
|
||||||
|
[loweralpha, start=3]
|
||||||
|
. Ergänzen Sie die Klasse `PrimeCheckerExecutor` so, dass für das Thread-Management jetzt vom ExecutorService erledigt wird.
|
||||||
|
Als Unterstützung sind entsprechende `TODO:` Komentare enthalten. +
|
||||||
|
[arabic]
|
||||||
|
.. Welche(r) Thread-Pool-Typ(en) eignet sich für diese Aufgabe?
|
||||||
|
.. Wie gross sollte der Thread-Pool sein um das beste Ergebnis zu erzeugen? +
|
||||||
|
Testen Sie mit unterschiedlichen Pool-Typen und Grössen.
|
||||||
|
. Stellen Sie sicher, dass der `ExecutorService` am Schluss korrekt heruntergefahren wird.
|
||||||
|
[arabic]
|
||||||
|
.. Wie viele Threads werden jetzt gestartet?
|
||||||
|
.. Was sehen sie bei den Laufzeiten?
|
||||||
|
|
||||||
|
Im Moment wird das Resultat nur auf der Konsole ausgegeben, da `Runnable` kein Resultat zurückgeben können.
|
||||||
|
Im nächsten Schritt soll die Anwendung so umgebaut werden, dass die Berechnung in einem Callable passiert und das Resultat im Hauptprogramm verarbeitet (in unserem Fall nur ausgegeben) wird.
|
||||||
|
|
||||||
|
[loweralpha, start=5]
|
||||||
|
. Ergänzen Sie die Klasse `PrimeTaskCallable` so, dass das Resultat der Berechnung zurückgegeben wird. +
|
||||||
|
Da die Berechnung asynchron erfolgt, können Sie im Hauptprogramm das Resultat nicht mehr so einfach der Zahl zuordnen, für welche die Berechnung gestartet wurde. Deshalb muss im Resultat neben dem Faktor auch die zugehörige Zahl enthalten sein. Dazu können Sie die innere statische Klasse `PrimeTaskCallable.Result` verwenden.
|
||||||
|
. Vervollständigen sie das Hauptprogramm in der Klasse `PrimeCheckerFuture`, welches nun `PrimeTaskCallable` verwenden soll. +
|
||||||
|
Das Resultat soll, wie bei `PrimeChecker`, auf der Konsole ausgegeben werden. Jetzt jedoch im Hauptprogramm.
|
||||||
|
[TIP]
|
||||||
|
Beachten Sie, dass das Übermitteln des Tasks an den `ExecutorService` unmittelbar ein Objekt vom Typ `Future` zurückgeliefert, in welchem das Resultat nach Beendigung des Tasks abgelegt wird. +
|
||||||
|
Um auf das Resultat zuzugreifen, ohne die Übermittlung des nächsten Tasks zu blockieren, müssen sie dieses `Future`-Objekt zwischenspeichern (z.B. in einer Liste). +
|
||||||
|
Später können sie die Resultate aus der Liste durchgehen und weiterverarbeiten, was in unserem Fall die Ausgabe auf der Konsole ist.
|
||||||
|
|
||||||
|
. Merken Sie einen Unterschied in den Berechnungszeiten oder im Verhalten der Ausgabe? Wenn ja, warum könnte das so sein?
|
||||||
|
|
||||||
|
== Bewertete Pflichtaufgaben
|
||||||
|
|
||||||
|
=== Mandelbrot [PA]
|
||||||
|
|
||||||
|
Die JavaFX-Anwendung `Mandelbrot` berechnet die Fraktaldarstellung eines Ausschnitts aus der https://de.wikipedia.org/wiki/Mandelbrot-Menge[Mandelbrot-Menge].
|
||||||
|
Dazu wird die zeilenweise Berechnung auf mehrere Threads aufgeteilt.
|
||||||
|
|
||||||
|
[NOTE]
|
||||||
|
Sie müssen die Mathematik hinter den Mandelbrotfraktalen nicht verstehen um die Aufgaben zu lösen.
|
||||||
|
|
||||||
|
[TIP]
|
||||||
|
Starten Sie die Anwendung mittels `gradle run` im Verzeichnis `Code/Mandelbrot` bzw. in der IDE mit dem Gradle run task.
|
||||||
|
Es kann sein, dass sie eine Fehlermeldung kriegen, wenn Sie die Mandelbrot-Klasse direkt in der IDE starten (Das ist ein bekanntes JavaFX-Problem).
|
||||||
|
|
||||||
|
Im GUI können Sie auswählen, wieviele Threads verwendet werden sollen. Zudem können Sie die Processor-Klasse wählen, die verwendet werden soll. Es gibt 3 Varianten:
|
||||||
|
|
||||||
|
* **`MandelbrotTaskProcessor`**: Verwendet ein Array von Worker-Threads die "konventionell" erzeugt und beendet werden. Das Fenster wird in so viel horizontale Bereiche (startRow .. endRow) aufgeteilt, wie Threads zur Verfügung stehen. Jeder Thread berechnet seinen zugewiesenen Zeilenbereich. +
|
||||||
|
Dieser Processor ist bereits umgesetzt.
|
||||||
|
* **`MandelbrotExecutorProcessor`**: Hier soll ein `ExecutorService` für das Management der Threads verwendet werden. `MandelbrotTask` soll als `Runnable` implementiert werden, das genau eine Zeile berechnet und diese dem GUI zur Ausgabe übergibt (`processorListener.rowProcessed(row)`). Es müssen also so viele Tasks erzeugt werden, wie das Fenster Zeilen hat (`height`). +
|
||||||
|
Das Grundgerüst der Klasse ist bereits vorhanden. Der ExecutorService muss ergänzt und `MandelbrotTask`-Klasse angepasst werden.
|
||||||
|
* **`MandelbrotCallableProcessor`**: Hier soll wiederum ein `ExecutorService` verwendet werden. Diesmal aber soll der `MandelbrotTask` als Callable umgesetzt werden, der jeweils eine Zeile als `Future<ImageRow>` zurückgibt.
|
||||||
|
Diese werden gesammelt und sobald verfügbar Zeilenweise ans GUI zur Ausgabe übergeben (`processorListener.rowProcessed(row)`). +
|
||||||
|
Das Grundgerüst der Klasse ist bereits vorhanden. Der ExecutorService muss ergänzt und `MandelbrotTask`-Klasse angepasst werden.
|
||||||
|
|
||||||
|
Das Thread-Handling ist in die `MandelbrotProcessor`-Klassen im Package `ch.zhaw.prog2.mandelbrot.processors` ausgelagert.
|
||||||
|
Sie müssen nur diese Klassen bearbeiten. Die Benutzeroberfläche und Hilfsklassen sind im übergeordneten
|
||||||
|
Package `ch.zhaw.prog2.mandelbrot` enthalten und müssen nicht angepasst werden.
|
||||||
|
|
||||||
|
Analysieren und testen Sie die Anwendung:
|
||||||
|
[loweralpha]
|
||||||
|
. Mit welcher Anzahl Threads erhalten Sie auf Ihrem Rechner die besten Resultate? +
|
||||||
|
[TIP]
|
||||||
|
Die Gesamtrechenzeit wird in der Konsole ausgegeben.
|
||||||
|
|
||||||
|
. Wie interpretieren Sie das Resultat im Verhältnis zur Anzahl Cores ihres Rechners?
|
||||||
|
|
||||||
|
==== Aufgabe
|
||||||
|
|
||||||
|
Ergänzen Sie die Klassen `MandelbrotExecutorProcessor` und `MandelbrotCallableExecutor`, sowie die jeweiligen
|
||||||
|
inneren Klassen `MandelbrotTask`, so dass diese die obige Beschreibung erfüllen.
|
||||||
|
|
||||||
|
==== Hinweise:
|
||||||
|
|
||||||
|
* Die Stellen die angepasst werden müssen sind mit TODO-Kommentaren versehen.
|
||||||
|
* Ü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.
|
||||||
|
* Überlegen Sie sich welche Optionen Sie haben, um auf die Resultate zu warten und sicherzustellen, dass alle ausgegeben wurden.
|
||||||
|
|
||||||
|
// Ende des Aufgabenblocks
|
||||||
|
:!sectnums:
|
||||||
|
// == Aufräumarbeiten
|
||||||
|
== 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.
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Gradle build configuration for specific lab module / exercise
|
||||||
|
*/
|
||||||
|
// the Java plugin is added by default in the main lab configuration
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
// Apply the application plugin to add support for building a CLI application.
|
||||||
|
id 'application'
|
||||||
|
// Adding JavaFX support and dependencies
|
||||||
|
id 'org.openjfx.javafxplugin' version '0.0.11'
|
||||||
|
}
|
||||||
|
// Project/Module information
|
||||||
|
description = 'Lab01 Mandelbrot'
|
||||||
|
group = 'ch.zhaw.prog2'
|
||||||
|
version = '2022.1'
|
||||||
|
|
||||||
|
// Dependency configuration
|
||||||
|
// repositories to download dependencies from
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
// required dependencies
|
||||||
|
dependencies {
|
||||||
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
|
||||||
|
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
|
||||||
|
testImplementation 'org.mockito:mockito-core:4.3.1'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test task configuration
|
||||||
|
test {
|
||||||
|
// Use JUnit platform for unit tests
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration for Application plugin
|
||||||
|
application {
|
||||||
|
// Define the main class for the application.
|
||||||
|
mainClass = 'ch.zhaw.prog2.mandelbrot.Mandelbrot'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration for JavaFX plugin
|
||||||
|
javafx {
|
||||||
|
version = '17'
|
||||||
|
modules = [ 'javafx.controls', 'javafx.fxml' ]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Java plugin configuration
|
||||||
|
java {
|
||||||
|
// By default the Java version of the gradle process is used as source/target version.
|
||||||
|
// This can be overridden, to ensure a specific version. Enable only if required.
|
||||||
|
// sourceCompatibility = JavaVersion.VERSION_17 // ensure Java source code compatibility
|
||||||
|
// targetCompatibility = JavaVersion.VERSION_17 // version of the created byte-code
|
||||||
|
|
||||||
|
// Java compiler specific options
|
||||||
|
compileJava {
|
||||||
|
// source files should be UTF-8 encoded
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
// for more options see https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.CompileOptions.html
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package ch.zhaw.prog2.mandelbrot;
|
||||||
|
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a container class which holds the data for one row to be drawn on the canvas.
|
||||||
|
* No getter and setters. Just use direct access to the fields.
|
||||||
|
*/
|
||||||
|
public class ImageRow {
|
||||||
|
public final int rowNumber;
|
||||||
|
public final Color[] pixels;
|
||||||
|
|
||||||
|
public ImageRow(int rowNumber, int width) {
|
||||||
|
this.rowNumber = rowNumber;
|
||||||
|
this.pixels = new Color[width];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package ch.zhaw.prog2.mandelbrot;
|
||||||
|
|
||||||
|
import javafx.application.Application;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This application uses several threads to compute an image "in the background".
|
||||||
|
*
|
||||||
|
* As rows of pixels in the image are computed, they are copied to the screen.
|
||||||
|
* (The image is a small piece of the famous Mandelbrot set, which
|
||||||
|
* is used just because it takes some time to compute. There is no need
|
||||||
|
* to understand what the image means.) The user starts the computation by
|
||||||
|
* clicking a "Start" button. A pop-up menu allows the user to select the
|
||||||
|
* number of threads to be used. The specified number of threads is created
|
||||||
|
* and each thread is assigned a region in the image. The threads are run
|
||||||
|
* at lower priority, which will make sure that the GUI thread will get a
|
||||||
|
* chance to run to repaint the display as necessary.
|
||||||
|
*/
|
||||||
|
public class Mandelbrot {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This Wrapper Class is only required to allow IDEs to start the FX-Applications
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Application.launch(MandelbrotGui.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // end Mandelbrot
|
|
@ -0,0 +1,206 @@
|
||||||
|
package ch.zhaw.prog2.mandelbrot;
|
||||||
|
|
||||||
|
import ch.zhaw.prog2.mandelbrot.processors.*;
|
||||||
|
import javafx.application.Application;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.canvas.Canvas;
|
||||||
|
import javafx.scene.canvas.GraphicsContext;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.ComboBox;
|
||||||
|
import javafx.scene.layout.BorderPane;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.stage.Screen;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This application uses several threads to compute an image "in the background".
|
||||||
|
*
|
||||||
|
* As rows of pixels in the image are computed, they are copied to the screen.
|
||||||
|
* (The image is a small piece of the famous Mandelbrot set, which
|
||||||
|
* is used just because it takes some time to compute. There is no need
|
||||||
|
* to understand what the image means.) The user starts the computation by
|
||||||
|
* clicking a "Start" button. A pop-up menu allows the user to select the
|
||||||
|
* number of threads to be used. The specified number of threads is created
|
||||||
|
* and each thread is assigned a region in the image. The threads are run
|
||||||
|
* at lower priority, which will make sure that the GUI thread will get a
|
||||||
|
* chance to run to repaint the display as necessary.
|
||||||
|
*/
|
||||||
|
public class MandelbrotGui extends Application implements MandelbrotProcessorListener {
|
||||||
|
// possible states of the GUI
|
||||||
|
private enum GuiState {START, RUNNING, STOPPING, STOPPED}
|
||||||
|
|
||||||
|
private GuiState state = GuiState.START;
|
||||||
|
|
||||||
|
private MandelbrotProcessor processor;
|
||||||
|
|
||||||
|
private Button startButton; // button the user can click to start or abort the thread
|
||||||
|
private ComboBox<String> threadCountSelect; // for specifying the number of threads to be used
|
||||||
|
private ComboBox<String> processorSelect; // for specifying the processor
|
||||||
|
|
||||||
|
private Canvas canvas; // the canvas where the image is displayed
|
||||||
|
private GraphicsContext g; // the graphics context for drawing on the canvas
|
||||||
|
|
||||||
|
private int width, height; // the size of the canvas
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up the GUI and event handling. The canvas will be 1200-by-1000 pixels,
|
||||||
|
* if that fits comfortably on the screen; otherwise, size will be reduced to fit.
|
||||||
|
*/
|
||||||
|
public void start(Stage stage) {
|
||||||
|
int screenWidth = (int) Screen.getPrimary().getVisualBounds().getWidth();
|
||||||
|
int screenHeight = (int) Screen.getPrimary().getVisualBounds().getHeight();
|
||||||
|
width = Math.min(1200, screenWidth - 50);
|
||||||
|
height = Math.min(1000, screenHeight - 120);
|
||||||
|
|
||||||
|
canvas = new Canvas(width, height);
|
||||||
|
g = canvas.getGraphicsContext2D();
|
||||||
|
g.setFill(Color.LIGHTGRAY);
|
||||||
|
g.fillRect(0, 0, width, height);
|
||||||
|
startButton = new Button("Start!");
|
||||||
|
startButton.setOnAction(e -> startOrStopProcessing());
|
||||||
|
// Because the tasks are CPU intensive, the default number of threads should be equal to the number of cores
|
||||||
|
int numCores = Runtime.getRuntime().availableProcessors();
|
||||||
|
int maxThreads = 2 * numCores;
|
||||||
|
threadCountSelect = new ComboBox<>();
|
||||||
|
threadCountSelect.setEditable(false);
|
||||||
|
for (int i = 1; i <= maxThreads; i++) {
|
||||||
|
threadCountSelect.getItems().add("Use " + i + " threads.");
|
||||||
|
}
|
||||||
|
// use the number of cores as default value
|
||||||
|
threadCountSelect.getSelectionModel().select(numCores - 1);
|
||||||
|
|
||||||
|
processorSelect = new ComboBox<>();
|
||||||
|
processorSelect.setEditable(false);
|
||||||
|
processorSelect.getItems().add("Use Thread-Processor");
|
||||||
|
processorSelect.getItems().add("Use Executor-Processor");
|
||||||
|
processorSelect.getItems().add("Use Callable-Processor");
|
||||||
|
processorSelect.getSelectionModel().select(0);
|
||||||
|
|
||||||
|
HBox bottom = new HBox(8, startButton, threadCountSelect, processorSelect);
|
||||||
|
bottom.setStyle("-fx-padding: 6px; -fx-border-color:black; -fx-border-width: 2px 0 0 0");
|
||||||
|
bottom.setAlignment(Pos.CENTER);
|
||||||
|
BorderPane root = new BorderPane(canvas);
|
||||||
|
root.setBottom(bottom);
|
||||||
|
root.setStyle("-fx-border-color:black; -fx-border-width: 2px");
|
||||||
|
Scene scene = new Scene(root);
|
||||||
|
stage.setScene(scene);
|
||||||
|
stage.setTitle("Mandelbrot");
|
||||||
|
stage.setResizable(false);
|
||||||
|
stage.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called when the user clicks the start button.
|
||||||
|
* If no computation is currently running, it starts processing with
|
||||||
|
* as many new threads as the user has specified.
|
||||||
|
* If a computation is in progress when this method is called,
|
||||||
|
* the processing is stopped and threads should be terminated.
|
||||||
|
*/
|
||||||
|
private void startOrStopProcessing() {
|
||||||
|
if (state == GuiState.RUNNING) {
|
||||||
|
setState(GuiState.STOPPING);
|
||||||
|
if (processor != null) {
|
||||||
|
processor.stopProcessing();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setState(GuiState.RUNNING);
|
||||||
|
int threadCount = threadCountSelect.getSelectionModel().getSelectedIndex() + 1;
|
||||||
|
processor = getProcessor();
|
||||||
|
System.out.println("Start processing using " + processor.getClass().getSimpleName() +
|
||||||
|
" with " + threadCount + " threads.");
|
||||||
|
// run processing in its own thread
|
||||||
|
new Thread(() -> processor.startProcessing(threadCount)).start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance of the selected MandelbrotProcessor implementation.
|
||||||
|
*
|
||||||
|
* @return new Instance of the selected MandelbrotProcessor implementation
|
||||||
|
*/
|
||||||
|
private MandelbrotProcessor getProcessor() {
|
||||||
|
MandelbrotProcessor processor;
|
||||||
|
int processorIndex = processorSelect.getSelectionModel().getSelectedIndex();
|
||||||
|
switch (processorIndex) {
|
||||||
|
case 0: processor = new MandelbrotTaskProcessor(this, width, height); break;
|
||||||
|
case 1: processor = new MandelbrotExecutorProcessor(this, width, height); break;
|
||||||
|
case 2: processor = new MandelbrotCallableProcessor(this, width, height); break;
|
||||||
|
default: processor = new MandelbrotTaskProcessor(this, width, height); break;
|
||||||
|
}
|
||||||
|
return processor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update GUI elements for a specific state
|
||||||
|
*
|
||||||
|
* @param newState the new state the GUI should be configured for
|
||||||
|
*/
|
||||||
|
private void setState(GuiState newState) {
|
||||||
|
this.state = newState;
|
||||||
|
switch (newState) {
|
||||||
|
case RUNNING:
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
startButton.setText("Abort"); // change name while computation is in progress
|
||||||
|
threadCountSelect.setDisable(true); // will be re-enabled when all threads finish
|
||||||
|
processorSelect.setDisable(true); // will be re-enabled when all threads finish
|
||||||
|
g.setFill(Color.LIGHTGRAY); // fill canvas with gray
|
||||||
|
g.fillRect(0, 0, width, height);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case STOPPING:
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
// prevent user from trying to stop threads that are already stopping
|
||||||
|
startButton.setDisable(true); // will be re-enabled when all threads have stopped
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case STOPPED:
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
// Make sure state is correct when threads end.
|
||||||
|
startButton.setText("Start Again");
|
||||||
|
startButton.setDisable(false);
|
||||||
|
threadCountSelect.setDisable(false);
|
||||||
|
processorSelect.setDisable(false);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called from the Processor when the processing is stopped or completed.
|
||||||
|
* GUI state needs to be reset.
|
||||||
|
*
|
||||||
|
* @param duration Duration of the processing in Milliseconds
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void processingStopped(long duration) {
|
||||||
|
this.setState(GuiState.STOPPED);
|
||||||
|
System.out.println("Finished processing after " + duration + "ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called from the Processor when a row has been processed
|
||||||
|
* and needs to be added to the image.
|
||||||
|
*
|
||||||
|
* @param row the row of pixels whose colors are to be set
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void rowProcessed(ImageRow row) {
|
||||||
|
if (row != null) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
for (int x = 0; x < row.pixels.length; x++) {
|
||||||
|
// Color an individual pixel by filling in a 1-by-1 pixel rectangle.
|
||||||
|
g.setFill(row.pixels[x]);
|
||||||
|
g.fillRect(x, row.rowNumber, 1, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // end MandelbrotGui
|
|
@ -0,0 +1,19 @@
|
||||||
|
package ch.zhaw.prog2.mandelbrot;
|
||||||
|
|
||||||
|
public interface MandelbrotProcessorListener {
|
||||||
|
/**
|
||||||
|
* This method is called from the Processor when the processing is stopped or completed.
|
||||||
|
* GUI state needs to be reset.
|
||||||
|
*
|
||||||
|
* @param duration Duration of the processing in Milliseconds
|
||||||
|
*/
|
||||||
|
void processingStopped(long duration);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called from the Processor when a row has been processed
|
||||||
|
* and needs to be added to the image.
|
||||||
|
*
|
||||||
|
* @param row the row of pixels whose colors are to be set
|
||||||
|
*/
|
||||||
|
void rowProcessed(ImageRow row);
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
package ch.zhaw.prog2.mandelbrot.processors;
|
||||||
|
|
||||||
|
import ch.zhaw.prog2.mandelbrot.ImageRow;
|
||||||
|
import ch.zhaw.prog2.mandelbrot.MandelbrotProcessorListener;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
public class MandelbrotCallableProcessor extends MandelbrotProcessor {
|
||||||
|
|
||||||
|
private volatile boolean terminate; // signal the threads to abort processing and terminate
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the Mandelbrot processor.
|
||||||
|
* This method also initializes the color palette, containing colors in spectral order.
|
||||||
|
* @param processorListener class to notify about processing results
|
||||||
|
* @param width with of the canvas in pixel
|
||||||
|
* @param height height of the canvas in pixel
|
||||||
|
*/
|
||||||
|
public MandelbrotCallableProcessor(MandelbrotProcessorListener processorListener, int width, int height) {
|
||||||
|
super(processorListener, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method starts as many new threads as the user has specified,
|
||||||
|
* and assigns a different part of the image to each thread.
|
||||||
|
* The threads are run at lower priority than the event-handling thread,
|
||||||
|
* in order to keep the GUI responsive.
|
||||||
|
*
|
||||||
|
* @param numThreads number of thread to start to run the tasks
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void startProcessing(int numThreads) {
|
||||||
|
terminate = false; // Set the signal before starting the threads!
|
||||||
|
super.tasksRemaining = height; // Records how many of the threads are still running
|
||||||
|
super.startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
// TODO: Start the the executor service with the given number of threads.
|
||||||
|
|
||||||
|
// TODO: process all rows using the Callable MandelbrotTask and store returned Futures in a list
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
long duration = System.currentTimeMillis()-startTime;
|
||||||
|
System.out.println("Tasks submitted after " + duration + "ms");
|
||||||
|
|
||||||
|
// TODO: get results from Future list and send them to the processListener (GUI)
|
||||||
|
// make sure to handle all Exceptions
|
||||||
|
try {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
// stop processing and shutdown executor
|
||||||
|
stopProcessing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stopp processing tasks and terminate all threads.
|
||||||
|
* Also notifies the GUI that the processing has been stopped.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void stopProcessing() {
|
||||||
|
terminate = true; // signal the threads to abort
|
||||||
|
// TODO: shutdown executor service
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// calculate processing time
|
||||||
|
long duration = System.currentTimeMillis() - startTime;
|
||||||
|
// notify the listener that the processing is completed
|
||||||
|
processorListener.processingStopped(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class defines the thread that does the computation.
|
||||||
|
* The run method computes the image one pixel at a time.
|
||||||
|
* After computing the colors for each row of pixels, the colors are
|
||||||
|
* copied into the image, and the part of the display that shows that
|
||||||
|
* row is repainted.
|
||||||
|
*
|
||||||
|
* Extended to implement also Callable.
|
||||||
|
* the call() method calculates and return only the result for the startRow.
|
||||||
|
*/
|
||||||
|
private class MandelbrotTask implements Callable<ImageRow> {
|
||||||
|
// TODO: Use Task implementation from MandelbrotExecutorProcessor change it to a Callable.
|
||||||
|
|
||||||
|
|
||||||
|
public ImageRow call() {
|
||||||
|
return null; // Compute one row of pixels.
|
||||||
|
}
|
||||||
|
|
||||||
|
} // end MandelbrotTask
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
package ch.zhaw.prog2.mandelbrot.processors;
|
||||||
|
|
||||||
|
import ch.zhaw.prog2.mandelbrot.ImageRow;
|
||||||
|
import ch.zhaw.prog2.mandelbrot.MandelbrotProcessorListener;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
public class MandelbrotExecutorProcessor extends MandelbrotProcessor {
|
||||||
|
|
||||||
|
private volatile boolean terminate; // signal the threads to abort processing and terminate
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the Mandelbrot processor.
|
||||||
|
* This method also initializes the color palette, containing colors in spectral order.
|
||||||
|
* @param processorListener class to notify about processing results
|
||||||
|
* @param width with of the canvas in pixel
|
||||||
|
* @param height height of the canvas in pixel
|
||||||
|
*/
|
||||||
|
public MandelbrotExecutorProcessor(MandelbrotProcessorListener processorListener, int width, int height) {
|
||||||
|
super(processorListener, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method starts as many new threads as the user has specified,
|
||||||
|
* and assigns a different part of the image to each thread.
|
||||||
|
* The threads are run at lower priority than the event-handling thread,
|
||||||
|
* in order to keep the GUI responsive.
|
||||||
|
*
|
||||||
|
* @param numThreads number of thread to start to run the tasks
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void startProcessing(int numThreads) {
|
||||||
|
terminate = false; // Set the signal before starting the threads!
|
||||||
|
super.tasksRemaining = height; // Records how many of the threads are still running
|
||||||
|
super.startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: Start the the executor service with the given number of threads.
|
||||||
|
|
||||||
|
// TODO: start a task for each row
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stopp processing tasks and terminate all threads.
|
||||||
|
* Also notifies the GUI that the processing has been stopped.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void stopProcessing() {
|
||||||
|
terminate = true; // signal the threads to abort
|
||||||
|
// TODO: shutdown executor service
|
||||||
|
|
||||||
|
// calculate processing time
|
||||||
|
long duration = System.currentTimeMillis() - startTime;
|
||||||
|
// notify the listener that the processing is completed
|
||||||
|
processorListener.processingStopped(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class defines the thread that does the computation.
|
||||||
|
* The run method computes the image one pixel at a time.
|
||||||
|
* After computing the colors for each row of pixels, the colors are
|
||||||
|
* copied into the image, and the part of the display that shows that
|
||||||
|
* row is repainted.
|
||||||
|
*/
|
||||||
|
private class MandelbrotTask implements Runnable {
|
||||||
|
// TODO: Use Task implementation from MandelbrotTaskProcessor and modify it to calculat only one row.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
}
|
||||||
|
} // end MandelbrotTask
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package ch.zhaw.prog2.mandelbrot.processors;
|
||||||
|
|
||||||
|
import ch.zhaw.prog2.mandelbrot.MandelbrotProcessorListener;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
|
public abstract class MandelbrotProcessor {
|
||||||
|
protected final MandelbrotProcessorListener processorListener;
|
||||||
|
protected javafx.scene.paint.Color[] palette; // the color palette, containing the colors of the spectrum
|
||||||
|
protected int width; // width of the canvas
|
||||||
|
protected int height; //height of the canvas
|
||||||
|
protected long startTime; // used to calculate the runtime for the calculation
|
||||||
|
protected int tasksRemaining; // How many tasks/threads are still running resp. need to be processed
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the Mandelbrot processor.
|
||||||
|
* This method also initializes the color palette, containing colors in spectral order.
|
||||||
|
* @param processorListener class to notify about processing results
|
||||||
|
* @param width with of the canvas in pixel
|
||||||
|
* @param height height of the canvas in pixel
|
||||||
|
*/
|
||||||
|
public MandelbrotProcessor(MandelbrotProcessorListener processorListener, int width, int height) {
|
||||||
|
this.processorListener = processorListener;
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
// initialize the color palette
|
||||||
|
this.palette = new Color[256];
|
||||||
|
for (int i = 0; i < 256; i++) {
|
||||||
|
this.palette[i] = Color.hsb(360 * (i / 256.0), 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method starts as many new threads as the user has specified,
|
||||||
|
* and assigns a different part of the image to each thread.
|
||||||
|
*
|
||||||
|
* @param numThreads number of thread to start to run the tasks
|
||||||
|
*/
|
||||||
|
public abstract void startProcessing(int numThreads);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stopp processing tasks and terminate all threads.
|
||||||
|
* Also notifies the GUI that the processing has been stopped.
|
||||||
|
*/
|
||||||
|
public abstract void stopProcessing();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called by each task/thread when it terminates. We keep track
|
||||||
|
* of the number of tasks/threads that have terminated, so that when they have
|
||||||
|
* all finished, we can put the program into the correct state, such as
|
||||||
|
* changing the name of the button to "Start Again" and re-enabling the
|
||||||
|
* pop-up menu.
|
||||||
|
*/
|
||||||
|
protected synchronized void taskFinished() {
|
||||||
|
tasksRemaining--;
|
||||||
|
if (tasksRemaining == 0) { // all threads have finished
|
||||||
|
stopProcessing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,156 @@
|
||||||
|
package ch.zhaw.prog2.mandelbrot.processors;
|
||||||
|
|
||||||
|
import ch.zhaw.prog2.mandelbrot.ImageRow;
|
||||||
|
import ch.zhaw.prog2.mandelbrot.MandelbrotProcessorListener;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
|
public class MandelbrotTaskProcessor extends MandelbrotProcessor {
|
||||||
|
|
||||||
|
private volatile boolean terminate; // signal the threads to abort processing and terminate
|
||||||
|
private java.lang.Thread[] workers; // the threads that compute the image
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the Mandelbrot processor.
|
||||||
|
* This method also initializes the color palette, containing colors in spectral order.
|
||||||
|
* @param processorListener class to notify about processing results
|
||||||
|
* @param width with of the canvas in pixel
|
||||||
|
* @param height height of the canvas in pixel
|
||||||
|
*/
|
||||||
|
public MandelbrotTaskProcessor(MandelbrotProcessorListener processorListener, int width, int height) {
|
||||||
|
super(processorListener, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method starts as many new threads as the user has specified,
|
||||||
|
* and assigns a different part of the image to each thread.
|
||||||
|
* The threads are run at lower priority than the event-handling thread,
|
||||||
|
* in order to keep the GUI responsive.
|
||||||
|
*
|
||||||
|
* @param numThreads number of thread to start to run the tasks
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void startProcessing(int numThreads) {
|
||||||
|
terminate = false; // Set the signal before starting the threads!
|
||||||
|
// use numThread tasks each calculating a range of rows
|
||||||
|
super.tasksRemaining = numThreads; // Records how many of the threads are still running
|
||||||
|
super.startTime = System.currentTimeMillis();
|
||||||
|
// calculate number of rows each task needs to calculate
|
||||||
|
int rowsPerThread = height / numThreads;
|
||||||
|
|
||||||
|
// Start the given number of threads and process ranges of rows
|
||||||
|
workers = new Thread[numThreads];
|
||||||
|
for (int i = 0; i < numThreads; i++) {
|
||||||
|
// first row computed by thread number i
|
||||||
|
int startRow = rowsPerThread * i;
|
||||||
|
|
||||||
|
// last row computed by thread number i
|
||||||
|
// (we have to make sure that the endRow for the last thread is the bottom row of the image)
|
||||||
|
int endRow = (i == numThreads - 1) ? height - 1 : rowsPerThread * (i + 1) - 1;
|
||||||
|
|
||||||
|
// Create and start a thread to compute the rows of the image from startRow to endRow.
|
||||||
|
workers[i] = new Thread(new MandelbrotTask(startRow, endRow));
|
||||||
|
try {
|
||||||
|
workers[i].setPriority(Thread.currentThread().getPriority() - 1);
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("Error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
workers[i].start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stopp processing tasks and terminate all threads.
|
||||||
|
* Also notifies the GUI that the processing has been stopped.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void stopProcessing() {
|
||||||
|
terminate = true; // signal the threads to abort
|
||||||
|
// shutdown / unset workers
|
||||||
|
workers = null;
|
||||||
|
// calculate processing time
|
||||||
|
long duration = System.currentTimeMillis() - startTime;
|
||||||
|
// notify the listener that the processing is completed
|
||||||
|
processorListener.processingStopped(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class defines the thread that does the computation.
|
||||||
|
* The run method computes the image one pixel at a time.
|
||||||
|
* After computing the colors for each row of pixels, the colors are
|
||||||
|
* copied into the image, and the part of the display that shows that
|
||||||
|
* row is repainted.
|
||||||
|
* All modifications to the GUI are made using Platform.runLater().
|
||||||
|
* (Since the thread runs in the background, at lower priority than
|
||||||
|
* the event-handling thread, the event-handling thread wakes up
|
||||||
|
* immediately to repaint the display.)
|
||||||
|
*/
|
||||||
|
private class MandelbrotTask implements Runnable {
|
||||||
|
// these values define the area and depth of the Mandelbrot graphic
|
||||||
|
// we keep them local to allow to extend the function to
|
||||||
|
// select the area and depth dynamically.
|
||||||
|
private final double xmin, xmax, ymin, ymax, dx, dy;
|
||||||
|
private final int maxIterations;
|
||||||
|
// this tasks calculates the following range of rows
|
||||||
|
private final int startRow, endRow;
|
||||||
|
|
||||||
|
/** initialize the Task to calculate a single row */
|
||||||
|
MandelbrotTask(int row) {
|
||||||
|
this(row, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** initialize the Task to calculate a range of rows */
|
||||||
|
MandelbrotTask(int startRow, int endRow) {
|
||||||
|
this.startRow = startRow;
|
||||||
|
this.endRow = endRow;
|
||||||
|
xmin = -1.6744096740931858;
|
||||||
|
xmax = -1.674409674093473;
|
||||||
|
ymin = 4.716540768697223E-5;
|
||||||
|
ymax = 4.716540790246652E-5;
|
||||||
|
dx = (xmax - xmin) / (width - 1);
|
||||||
|
dy = (ymax - ymin) / (height - 1);
|
||||||
|
maxIterations = 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
for (int row = startRow; row <= endRow; row++) {
|
||||||
|
// Compute one row of pixels.
|
||||||
|
ImageRow imageRow = calculateRow(row);
|
||||||
|
// Check for the signal to immediately abort the computation.
|
||||||
|
if (terminate || imageRow == null) return;
|
||||||
|
// notify the listener about the processed image row
|
||||||
|
processorListener.rowProcessed(imageRow);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
// Make sure this is called when the task finishes for any reason.
|
||||||
|
taskFinished(); // notify task completion; calls stopProcessing() when last tasks completed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImageRow calculateRow(int row) {
|
||||||
|
final ImageRow imageRow = new ImageRow(row, width);
|
||||||
|
double x;
|
||||||
|
double y = ymax - dy * row;
|
||||||
|
|
||||||
|
for (int col = 0; col < width; col++) {
|
||||||
|
x = xmin + dx * col;
|
||||||
|
int count = 0;
|
||||||
|
double xx = x;
|
||||||
|
double yy = y;
|
||||||
|
while (count < maxIterations && (xx * xx + yy * yy) < 4) {
|
||||||
|
count++;
|
||||||
|
double newxx = xx * xx - yy * yy + x;
|
||||||
|
yy = 2 * xx * yy + y;
|
||||||
|
xx = newxx;
|
||||||
|
}
|
||||||
|
// select color based on count of iterations
|
||||||
|
imageRow.pixels[col] = (count != maxIterations) ?
|
||||||
|
palette[count % palette.length] : Color.BLACK;
|
||||||
|
// Check for the signal to immediately abort the computation.
|
||||||
|
if (terminate) return null;
|
||||||
|
}
|
||||||
|
return imageRow;
|
||||||
|
}
|
||||||
|
} // end MandelbrotTask
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
package ch.zhaw.prog2.mandelbrot;
|
||||||
|
|
||||||
|
import ch.zhaw.prog2.mandelbrot.processors.MandelbrotCallableProcessor;
|
||||||
|
import ch.zhaw.prog2.mandelbrot.processors.MandelbrotExecutorProcessor;
|
||||||
|
import ch.zhaw.prog2.mandelbrot.processors.MandelbrotProcessor;
|
||||||
|
import ch.zhaw.prog2.mandelbrot.processors.MandelbrotTaskProcessor;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
public class MandelbrotMockTest {
|
||||||
|
private static final int width = 10;
|
||||||
|
private static final int height = 5;
|
||||||
|
|
||||||
|
@Mock private MandelbrotProcessorListener listener;
|
||||||
|
|
||||||
|
private MandelbrotProcessorListener mandelbrotProcessorListenerTester = new MandelbrotProcessorListener() {
|
||||||
|
@Override
|
||||||
|
public void processingStopped(long duration) {
|
||||||
|
System.out.println("Duration(ms): " + duration);
|
||||||
|
assertNotEquals(0,duration, "Duration must not be 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rowProcessed(ImageRow row) {
|
||||||
|
assertNotNull(row);
|
||||||
|
assertNotNull(row.pixels);
|
||||||
|
assertEquals(width, row.pixels.length, "Length of Pixel-Array not matching");
|
||||||
|
System.out.print("row-nr: " + row.rowNumber + " ");
|
||||||
|
System.out.print("row-pixels: " + Arrays.stream(row.pixels)
|
||||||
|
.map(Color::toString).collect(Collectors.joining(", ")));
|
||||||
|
System.out.println();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
MockitoAnnotations.openMocks(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMandelbrotTaskProcessorListenerCalls() throws InterruptedException {
|
||||||
|
MandelbrotProcessor processor = new MandelbrotTaskProcessor(listener, width, height);
|
||||||
|
processor.startProcessing(1);
|
||||||
|
Thread.sleep(2000);
|
||||||
|
verify(listener, times(height)).rowProcessed(any(ImageRow.class));
|
||||||
|
verify(listener, times(1)).processingStopped(anyLong());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMandelbrotTaskProcessorListenerContent() throws InterruptedException {
|
||||||
|
MandelbrotProcessor processor = new MandelbrotTaskProcessor(mandelbrotProcessorListenerTester, width, height);
|
||||||
|
processor.startProcessing(1);
|
||||||
|
Thread.sleep(2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMandelbrotExecutorProcessorListenerCalls() throws InterruptedException {
|
||||||
|
MandelbrotProcessor processor = new MandelbrotExecutorProcessor(listener, width, height);
|
||||||
|
processor.startProcessing(1);
|
||||||
|
Thread.sleep(2000);
|
||||||
|
verify(listener, times(height)).rowProcessed(any(ImageRow.class));
|
||||||
|
verify(listener, times(1)).processingStopped(anyLong());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMandelbrotExecutorProcessorListenerContent() throws InterruptedException {
|
||||||
|
MandelbrotProcessor processor = new MandelbrotExecutorProcessor(mandelbrotProcessorListenerTester, width, height);
|
||||||
|
processor.startProcessing(1);
|
||||||
|
Thread.sleep(2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMandelbrotCallableProcessorListenerCalls() throws InterruptedException {
|
||||||
|
MandelbrotProcessor processor = new MandelbrotCallableProcessor(listener, width, height);
|
||||||
|
processor.startProcessing(1);
|
||||||
|
Thread.sleep(2000);
|
||||||
|
verify(listener, times(height)).rowProcessed(any(ImageRow.class));
|
||||||
|
verify(listener, times(1)).processingStopped(anyLong());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMandelbrotCallableProcessorListenerContent() throws InterruptedException {
|
||||||
|
MandelbrotProcessor processor = new MandelbrotCallableProcessor(mandelbrotProcessorListenerTester, width, height);
|
||||||
|
processor.startProcessing(1);
|
||||||
|
Thread.sleep(2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Gradle build configuration for specific lab module / exercise
|
||||||
|
*/
|
||||||
|
// the Java plugin is added by default in the main lab configuration
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project/Module information
|
||||||
|
description = 'Lab01 PrimeChecker'
|
||||||
|
group = 'ch.zhaw.prog2'
|
||||||
|
version = '2022.1'
|
||||||
|
|
||||||
|
// Dependency configuration
|
||||||
|
// repositories to download dependencies from
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
// required dependencies
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Java plugin configuration
|
||||||
|
java {
|
||||||
|
// By default the Java version of the gradle process is used as source/target version.
|
||||||
|
// This can be overridden, to ensure a specific version. Enable only if required.
|
||||||
|
// sourceCompatibility = JavaVersion.VERSION_17 // ensure Java source code compatibility
|
||||||
|
// targetCompatibility = JavaVersion.VERSION_17 // version of the created byte-code
|
||||||
|
|
||||||
|
// Java compiler specific options
|
||||||
|
compileJava {
|
||||||
|
// source files should be UTF-8 encoded
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
// for more options see https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.CompileOptions.html
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package ch.zhaw.prog2.primechecker;
|
||||||
|
|
||||||
|
public class PrimeChecker {
|
||||||
|
|
||||||
|
private static final long LOWER_LIMIT = 10000L;
|
||||||
|
private static final long UPPER_LIMIT = 1000000000L;
|
||||||
|
private static final int NUM_PRIME = 500;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
long starttime = System.currentTimeMillis();
|
||||||
|
long duration;
|
||||||
|
try {
|
||||||
|
checkPrimes(NUM_PRIME);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
System.out.println("Interrupted - " + e.getMessage());
|
||||||
|
} finally {
|
||||||
|
duration = System.currentTimeMillis() - starttime;
|
||||||
|
}
|
||||||
|
System.out.println("Finished in " + duration + " ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkPrimes(int numPrimes) throws InterruptedException {
|
||||||
|
for (int i = 0; i < numPrimes; i++) {
|
||||||
|
new PrimeTask(nextRandom()).run(); // runs sequential in current thread
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long nextRandom() {
|
||||||
|
return LOWER_LIMIT + (long)(Math.random() * (UPPER_LIMIT - LOWER_LIMIT));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package ch.zhaw.prog2.primechecker;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class PrimeCheckerExecutor {
|
||||||
|
|
||||||
|
private static final long LOWER_LIMIT = 10000L;
|
||||||
|
private static final long UPPER_LIMIT = 1000000000L;
|
||||||
|
private static final int NUM_PRIME = 500;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
long starttime = System.currentTimeMillis();
|
||||||
|
long duration;
|
||||||
|
try {
|
||||||
|
checkPrimes(NUM_PRIME);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
System.out.println("Interrupted - " + e.getMessage());
|
||||||
|
} finally {
|
||||||
|
duration = System.currentTimeMillis() - starttime;
|
||||||
|
}
|
||||||
|
System.out.println("Finished in " + duration + " ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkPrimes(int numPrimes) throws InterruptedException {
|
||||||
|
// TODO: create ExecutorService - What ThreadPool-Type/-Size fits best?
|
||||||
|
|
||||||
|
for (int i = 0; i < numPrimes; i++) {
|
||||||
|
// TODO: execute the runnable using the executor service
|
||||||
|
|
||||||
|
}
|
||||||
|
// stop ExecutorService
|
||||||
|
|
||||||
|
// wait for termination with timeout of 1 minute
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long nextRandom() {
|
||||||
|
return LOWER_LIMIT + (long)(Math.random() * (UPPER_LIMIT - LOWER_LIMIT));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package ch.zhaw.prog2.primechecker;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
public class PrimeCheckerFuture {
|
||||||
|
|
||||||
|
private static final long LOWER_LIMIT = 10000L;
|
||||||
|
private static final long UPPER_LIMIT = 1000000000L;
|
||||||
|
private static final int NUM_PRIME = 500;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
long starttime = System.currentTimeMillis();
|
||||||
|
long duration;
|
||||||
|
try {
|
||||||
|
checkPrimes(NUM_PRIME);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
System.out.println("Interrupted - " + e.getMessage());
|
||||||
|
} finally {
|
||||||
|
duration = System.currentTimeMillis() - starttime;
|
||||||
|
}
|
||||||
|
System.out.println("Finished in " + duration + " ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkPrimes(int numPrimes) throws InterruptedException {
|
||||||
|
// TODO: create ExecutorService
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: submit tasks to ExecutorService and collect the returned Futures in a List
|
||||||
|
for (int i = 0; i < numPrimes; i++) {
|
||||||
|
|
||||||
|
}
|
||||||
|
// TODO: Loop through List, wait for completion and print results
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: stop ExecutorService
|
||||||
|
|
||||||
|
// TODO: await termination with timeout 1 minute
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long nextRandom() {
|
||||||
|
return LOWER_LIMIT + (long)(Math.random() * (UPPER_LIMIT - LOWER_LIMIT));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package ch.zhaw.prog2.primechecker;
|
||||||
|
|
||||||
|
public class PrimeTask implements Runnable {
|
||||||
|
|
||||||
|
private final long primeCandidate;
|
||||||
|
|
||||||
|
public PrimeTask(long primeCandidate) {
|
||||||
|
this.primeCandidate = primeCandidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
long smallestFactor = findSmallestFactor(primeCandidate);
|
||||||
|
System.out.println("Number: " + this.primeCandidate + " -> " +
|
||||||
|
( smallestFactor == 0 ? "PRIME" : "Factor: " + smallestFactor ));
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Brute force check if submitted candidate is a prime number.
|
||||||
|
*
|
||||||
|
* @param primeCandidate Number to check if it is a
|
||||||
|
* @return 0 if prime number, smallest factor otherwise
|
||||||
|
*/
|
||||||
|
private long findSmallestFactor(long primeCandidate) {
|
||||||
|
if (primeCandidate>3) {
|
||||||
|
for(long factor = 2; factor <=primeCandidate/2; ++factor) {
|
||||||
|
if ( primeCandidate / factor * factor == primeCandidate) {
|
||||||
|
return factor; // found a factor -> is no prime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0; // is prime number
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package ch.zhaw.prog2.primechecker;
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
public class PrimeTaskCallable implements Callable<PrimeTaskCallable.Result> {
|
||||||
|
|
||||||
|
private final long primeCandidate;
|
||||||
|
|
||||||
|
public PrimeTaskCallable(long primeCandidate) {
|
||||||
|
this.primeCandidate = primeCandidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Result call() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Brute force check if submitted candidate is a prime number.
|
||||||
|
*
|
||||||
|
* @param primeCandidate Number to check if it is a
|
||||||
|
* @return 0 if prime number, smallest factor otherwise
|
||||||
|
*/
|
||||||
|
private long findSmallestFactor(long primeCandidate) {
|
||||||
|
if (primeCandidate>3) {
|
||||||
|
for(long factor = 2; factor <= primeCandidate/2; ++factor) {
|
||||||
|
if ( primeCandidate / factor * factor == primeCandidate) {
|
||||||
|
return factor; // found a factor -> is no prime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0; // is prime number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Small static helper class serving as a container to return the result.
|
||||||
|
* No accessor methods. Use direct access to fields to read values.
|
||||||
|
* (Starting from Java14, we could use Java Records for this)
|
||||||
|
*/
|
||||||
|
public static class Result {
|
||||||
|
public final long candidate;
|
||||||
|
public final long factor;
|
||||||
|
public final boolean isPrime;
|
||||||
|
|
||||||
|
public Result(long candidate, long factor) {
|
||||||
|
this.candidate = candidate;
|
||||||
|
this.factor = factor;
|
||||||
|
this.isPrime = factor == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Gradle build configuration for specific lab module / exercise
|
||||||
|
*/
|
||||||
|
// the Java plugin is added by default in the main lab configuration
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project/Module information
|
||||||
|
description = 'Lab01 Printer'
|
||||||
|
group = 'ch.zhaw.prog2'
|
||||||
|
version = '2022.1'
|
||||||
|
|
||||||
|
// Dependency configuration
|
||||||
|
// repositories to download dependencies from
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
// required dependencies
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Java plugin configuration
|
||||||
|
java {
|
||||||
|
// By default the Java version of the gradle process is used as source/target version.
|
||||||
|
// This can be overridden, to ensure a specific version. Enable only if required.
|
||||||
|
// sourceCompatibility = JavaVersion.VERSION_17 // ensure Java source code compatibility
|
||||||
|
// targetCompatibility = JavaVersion.VERSION_17 // version of the created byte-code
|
||||||
|
|
||||||
|
// Java compiler specific options
|
||||||
|
compileJava {
|
||||||
|
// source files should be UTF-8 encoded
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
// for more options see https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.CompileOptions.html
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package ch.zhaw.prog2.printer;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Used to set properties for gradle builds
|
||||||
|
# (see https://dev.to/jmfayard/configuring-gradle-with-gradle-properties-211k)
|
||||||
|
|
||||||
|
# gradle configuration
|
||||||
|
# (https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties)
|
||||||
|
#org.gradle.warning.mode=(all,fail,summary,none) default: summary
|
||||||
|
org.gradle.warning.mode=all
|
Binary file not shown.
|
@ -0,0 +1,5 @@
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
|
@ -0,0 +1,234 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright © 2015-2021 the original authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
app_path=$0
|
||||||
|
|
||||||
|
# Need this for daisy-chained symlinks.
|
||||||
|
while
|
||||||
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
|
[ -h "$app_path" ]
|
||||||
|
do
|
||||||
|
ls=$( ls -ld "$app_path" )
|
||||||
|
link=${ls#*' -> '}
|
||||||
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD=maximum
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "$( uname )" in #(
|
||||||
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
|
Darwin* ) darwin=true ;; #(
|
||||||
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
|
NONSTOP* ) nonstop=true ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
|
else
|
||||||
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD=java
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
|
case $MAX_FD in #(
|
||||||
|
max*)
|
||||||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
|
warn "Could not query maximum file descriptor limit"
|
||||||
|
esac
|
||||||
|
case $MAX_FD in #(
|
||||||
|
'' | soft) :;; #(
|
||||||
|
*)
|
||||||
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
|
# * args from the command line
|
||||||
|
# * the main class name
|
||||||
|
# * -classpath
|
||||||
|
# * -D...appname settings
|
||||||
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command;
|
||||||
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
|
# double quotes to make sure that they get re-expanded; and
|
||||||
|
# * put everything else in single quotes, so that it's not re-expanded.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-classpath "$CLASSPATH" \
|
||||||
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
|
@ -0,0 +1,89 @@
|
||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
Binary file not shown.
After Width: | Height: | Size: 121 KiB |
Binary file not shown.
After Width: | Height: | Size: 80 KiB |
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Dynamic Multi-Module project structure
|
||||||
|
* automatically adds each exercise as a sub-project (module)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// use current directory name as root project name
|
||||||
|
rootProject.name = file('.').toPath().fileName
|
||||||
|
|
||||||
|
// dynamically add sub-projects in handout folder
|
||||||
|
File handoutDir = file('code')
|
||||||
|
if (handoutDir.isDirectory()) {
|
||||||
|
handoutDir.eachDir { dir ->
|
||||||
|
String subProjectName = ":${dir.name}"
|
||||||
|
include(subProjectName)
|
||||||
|
project(subProjectName).projectDir = dir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dynamically add sub-projects in solutions* folders
|
||||||
|
//List<File> solutionDirs = List.of(file('.').listFiles((File dir, String name) -> name.startsWith("solutions")))
|
||||||
|
file('.').eachDirMatch( name -> name.startsWith('solutions')) { solutionDir ->
|
||||||
|
if (solutionDir.isDirectory()) {
|
||||||
|
solutionDir.eachDir { dir ->
|
||||||
|
if (!dir.name.equals('images')) {
|
||||||
|
String subProjectName = ":${dir.name}-sol"
|
||||||
|
include(subProjectName)
|
||||||
|
project(subProjectName).projectDir = dir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lab preparation tasks
|
||||||
|
File classroomDir = file('classroom')
|
||||||
|
if (classroomDir.isDirectory()) {
|
||||||
|
String subProjectName = ":${classroomDir.name}"
|
||||||
|
include(subProjectName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example: manually adding sub-project with name == folder
|
||||||
|
//include 'module1'
|
||||||
|
|
||||||
|
// Example: manually adding sub-projects with different name & folder
|
||||||
|
//include(':lab00-module1')
|
||||||
|
//project(':lab00-module1').projectDir = file('handout/module1')
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Gradle build configuration for specific lab module / exercise
|
||||||
|
*/
|
||||||
|
// the Java plugin is added by default in the main lab configuration
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project/Module information
|
||||||
|
description = 'Lab01 PrimeChecker Solution'
|
||||||
|
group = 'ch.zhaw.prog2'
|
||||||
|
version = '2022.1'
|
||||||
|
|
||||||
|
// Dependency configuration
|
||||||
|
// repositories to download dependencies from
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
// required dependencies
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Java plugin configuration
|
||||||
|
java {
|
||||||
|
// By default the Java version of the gradle process is used as source/target version.
|
||||||
|
// This can be overridden, to ensure a specific version. Enable only if required.
|
||||||
|
// sourceCompatibility = JavaVersion.VERSION_17 // ensure Java source code compatibility
|
||||||
|
// targetCompatibility = JavaVersion.VERSION_17 // version of the created byte-code
|
||||||
|
|
||||||
|
// Java compiler specific options
|
||||||
|
compileJava {
|
||||||
|
// source files should be UTF-8 encoded
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
// for more options see https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.CompileOptions.html
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package ch.zhaw.prog2.primechecker;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class PrimeChecker {
|
||||||
|
|
||||||
|
private static final long LOWER_LIMIT = 10000L;
|
||||||
|
private static final long UPPER_LIMIT = 1000000000L;
|
||||||
|
private static final int NUM_PRIME = 500;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
long starttime = System.currentTimeMillis();
|
||||||
|
long duration;
|
||||||
|
try {
|
||||||
|
checkPrimes(NUM_PRIME);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
System.out.println("Interrupted - " + e.getMessage());
|
||||||
|
} finally {
|
||||||
|
duration = System.currentTimeMillis() - starttime;
|
||||||
|
}
|
||||||
|
System.out.println("Finished in " + duration + " ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkPrimes(int numPrimes) throws InterruptedException {
|
||||||
|
List<Thread> tasks = new ArrayList<>();
|
||||||
|
for (int i = 0; i < numPrimes; i++) {
|
||||||
|
//new PrimeTask(nextRandom()).run(); // runs sequential in current thread
|
||||||
|
Thread task = new Thread(new PrimeTask(nextRandom()));
|
||||||
|
tasks.add(task);
|
||||||
|
task.start();
|
||||||
|
}
|
||||||
|
for (Thread task: tasks) {
|
||||||
|
task.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long nextRandom() {
|
||||||
|
return LOWER_LIMIT + (long)(Math.random() * (UPPER_LIMIT - LOWER_LIMIT));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package ch.zhaw.prog2.primechecker;
|
||||||
|
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
public class PrimeCheckerExecutor {
|
||||||
|
|
||||||
|
private static final long LOWER_LIMIT = 10000L;
|
||||||
|
private static final long UPPER_LIMIT = 1000000000L;
|
||||||
|
private static final int NUM_PRIME = 500;
|
||||||
|
private static StringWriter statsWriter = new StringWriter();
|
||||||
|
private static PrintWriter stats = new PrintWriter(statsWriter);
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
long starttime = System.currentTimeMillis();
|
||||||
|
long duration;
|
||||||
|
try {
|
||||||
|
checkPrimes(NUM_PRIME);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
System.out.println("Interrupted - " + e.getMessage());
|
||||||
|
} finally {
|
||||||
|
duration = System.currentTimeMillis() - starttime;
|
||||||
|
}
|
||||||
|
System.out.println("Finished in " + duration + " ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkPrimes(int numPrimes) throws InterruptedException {
|
||||||
|
// determine number of cores
|
||||||
|
int numCores = Runtime.getRuntime().availableProcessors();
|
||||||
|
// create ExecutorService
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(numCores);
|
||||||
|
// ExecutorService executor = Executors.newCachedThreadPool();
|
||||||
|
|
||||||
|
for (int i = 0; i < numPrimes; i++) {
|
||||||
|
// execute the runnable using the executor service
|
||||||
|
executor.execute(new PrimeTask(nextRandom()));
|
||||||
|
// collect executor statistics every 100 tasks
|
||||||
|
if (i % 100 == 0) stats.println("Executor Info after " + i + " tasks: " + executor);
|
||||||
|
}
|
||||||
|
// stop ExecutorService
|
||||||
|
executor.shutdown();
|
||||||
|
// wait for termination with timeout of 1 minute
|
||||||
|
executor.awaitTermination(1, TimeUnit.MINUTES);
|
||||||
|
stats.println("Executor Info after termination: " + executor);
|
||||||
|
// print executor statistics
|
||||||
|
System.out.print(statsWriter.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long nextRandom() {
|
||||||
|
return LOWER_LIMIT + (long)(Math.random() * (UPPER_LIMIT - LOWER_LIMIT));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package ch.zhaw.prog2.primechecker;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
public class PrimeCheckerFuture {
|
||||||
|
|
||||||
|
private static final long LOWER_LIMIT = 10000L;
|
||||||
|
private static final long UPPER_LIMIT = 1000000000L;
|
||||||
|
private static final int NUM_PRIME = 500;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
long starttime = System.currentTimeMillis();
|
||||||
|
long duration;
|
||||||
|
try {
|
||||||
|
checkPrimes(NUM_PRIME);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
System.out.println("Interrupted - " + e.getMessage());
|
||||||
|
} finally {
|
||||||
|
duration = System.currentTimeMillis() - starttime;
|
||||||
|
}
|
||||||
|
System.out.println("Finished in " + duration + " ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkPrimes(int numPrimes) throws InterruptedException {
|
||||||
|
// create ExecutorService
|
||||||
|
int numCores = Runtime.getRuntime().availableProcessors();
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(numCores);
|
||||||
|
// submit tasks to ExecutorService and collect the returned Futures in a List
|
||||||
|
List<Future<PrimeTaskCallable.Result>> tasks = new ArrayList<>();
|
||||||
|
for (int i = 0; i < numPrimes; i++) {
|
||||||
|
// execute runnable / submit callable
|
||||||
|
tasks.add(executor.submit(new PrimeTaskCallable(nextRandom())));
|
||||||
|
}
|
||||||
|
// Loop through List, wait for completion and print results
|
||||||
|
for (Future<PrimeTaskCallable.Result> task : tasks) {
|
||||||
|
try {
|
||||||
|
PrimeTaskCallable.Result result = task.get();
|
||||||
|
System.out.println("Number: " + result.candidate + " -> " +
|
||||||
|
(result.isPrime ? "PRIME" : "Factor: " + result.factor));
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
System.out.println("Execution failed : " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// stop ExecutorService
|
||||||
|
executor.shutdown();
|
||||||
|
// await termination with timeout 1 minute
|
||||||
|
executor.awaitTermination(1, TimeUnit.MINUTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long nextRandom() {
|
||||||
|
return LOWER_LIMIT + (long)(Math.random() * (UPPER_LIMIT - LOWER_LIMIT));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package ch.zhaw.prog2.primechecker;
|
||||||
|
|
||||||
|
public class PrimeTask implements Runnable {
|
||||||
|
|
||||||
|
private final long primeCandidate;
|
||||||
|
|
||||||
|
public PrimeTask(long primeCandidate) {
|
||||||
|
this.primeCandidate = primeCandidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
long smallestFactor = findSmallestFactor(primeCandidate);
|
||||||
|
System.out.println("Number: " + this.primeCandidate + " -> " +
|
||||||
|
( smallestFactor == 0 ? "PRIME" : "Factor: " + smallestFactor ));
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Brute force check if submitted candidate is a prime number.
|
||||||
|
*
|
||||||
|
* @param primeCandidate Number to check if it is a
|
||||||
|
* @return 0 if prime number, smallest factor otherwise
|
||||||
|
*/
|
||||||
|
private long findSmallestFactor(long primeCandidate) {
|
||||||
|
if (primeCandidate>3) {
|
||||||
|
for(long factor = 2; factor <=primeCandidate/2; ++factor) {
|
||||||
|
if ( primeCandidate / factor * factor == primeCandidate) {
|
||||||
|
return factor; // found a factor -> is no prime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0; // is prime number
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package ch.zhaw.prog2.primechecker;
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
public class PrimeTaskCallable implements Callable<PrimeTaskCallable.Result> {
|
||||||
|
|
||||||
|
private final long primeCandidate;
|
||||||
|
|
||||||
|
public PrimeTaskCallable(long primeCandidate) {
|
||||||
|
this.primeCandidate = primeCandidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result call() {
|
||||||
|
return new Result(primeCandidate, findSmallestFactor(primeCandidate));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Brute force check if submitted candidate is a prime number.
|
||||||
|
*
|
||||||
|
* @param primeCandidate Number to check if it is a
|
||||||
|
* @return 0 if prime number, smallest factor otherwise
|
||||||
|
*/
|
||||||
|
private long findSmallestFactor(long primeCandidate) {
|
||||||
|
if (primeCandidate>3) {
|
||||||
|
for(long factor = 2; factor <= primeCandidate/2; ++factor) {
|
||||||
|
if ( primeCandidate / factor * factor == primeCandidate) {
|
||||||
|
return factor; // found a factor -> is no prime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0; // is prime number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Small static helper class serving as a container to return the result.
|
||||||
|
* No accessor methods. Use direct access to fields to read values.
|
||||||
|
* (Starting from Java14, we could use Java Records for this)
|
||||||
|
*/
|
||||||
|
public static class Result {
|
||||||
|
public final long candidate;
|
||||||
|
public final long factor;
|
||||||
|
public final boolean isPrime;
|
||||||
|
|
||||||
|
public Result(long candidate, long factor) {
|
||||||
|
this.candidate = candidate;
|
||||||
|
this.factor = factor;
|
||||||
|
this.isPrime = factor == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Gradle build configuration for specific lab module / exercise
|
||||||
|
*/
|
||||||
|
// the Java plugin is added by default in the main lab configuration
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project/Module information
|
||||||
|
description = 'Lab01 Printer Solution'
|
||||||
|
group = 'ch.zhaw.prog2'
|
||||||
|
version = '2022.1'
|
||||||
|
|
||||||
|
// Dependency configuration
|
||||||
|
// repositories to download dependencies from
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
// required dependencies
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Java plugin configuration
|
||||||
|
java {
|
||||||
|
// By default the Java version of the gradle process is used as source/target version.
|
||||||
|
// This can be overridden, to ensure a specific version. Enable only if required.
|
||||||
|
// sourceCompatibility = JavaVersion.VERSION_17 // ensure Java source code compatibility
|
||||||
|
// targetCompatibility = JavaVersion.VERSION_17 // version of the created byte-code
|
||||||
|
|
||||||
|
// Java compiler specific options
|
||||||
|
compileJava {
|
||||||
|
// source files should be UTF-8 encoded
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
// for more options see https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.CompileOptions.html
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package ch.zhaw.prog2.printer;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package ch.zhaw.prog2.printer;
|
||||||
|
|
||||||
|
|
||||||
|
public class PrinterLsgA {
|
||||||
|
|
||||||
|
// test program
|
||||||
|
public static void main(String[] arg) {
|
||||||
|
PrinterThread a = new PrinterThread("PrinterA", '.', 0);
|
||||||
|
PrinterThread b = new PrinterThread("PrinterB", '*', 0);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (this != currentThread()) {
|
||||||
|
throw new IllegalStateException("run() must not be called directly");
|
||||||
|
}
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package ch.zhaw.prog2.printer;
|
||||||
|
|
||||||
|
|
||||||
|
public class PrinterLsgB {
|
||||||
|
|
||||||
|
// test program
|
||||||
|
public static void main(String[] arg) {
|
||||||
|
PrinterRunnable a = new PrinterRunnable('.', 0);
|
||||||
|
PrinterRunnable b = new PrinterRunnable('*', 0);
|
||||||
|
Thread t1 = new Thread(a, "PrinterA");
|
||||||
|
Thread t2 = new Thread(b, "PrinterB");
|
||||||
|
t1.start();
|
||||||
|
t2.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PrinterRunnable implements Runnable {
|
||||||
|
char symbol;
|
||||||
|
int sleepTime;
|
||||||
|
|
||||||
|
public PrinterRunnable(char symbol, int sleepTime) {
|
||||||
|
this.symbol = symbol;
|
||||||
|
this.sleepTime = sleepTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
Thread current = Thread.currentThread();
|
||||||
|
System.out.println(current.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' + current.getName() + " run ended.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package ch.zhaw.prog2.printer;
|
||||||
|
|
||||||
|
|
||||||
|
public class PrinterLsgCD {
|
||||||
|
|
||||||
|
// test program
|
||||||
|
public static void main(String[] arg) {
|
||||||
|
PrinterRunnable a = new PrinterRunnable('.', 0); // different if you change the sleep
|
||||||
|
// time?
|
||||||
|
PrinterRunnable b = new PrinterRunnable('*', 0); // different if you change the sleep
|
||||||
|
// time?
|
||||||
|
Thread t1 = new Thread(a, "PrinterA");
|
||||||
|
Thread t2 = new Thread(b, "PrinterB");
|
||||||
|
t1.start();
|
||||||
|
t2.start();
|
||||||
|
// d)
|
||||||
|
try {
|
||||||
|
t1.join(); // wait for t1 to die
|
||||||
|
t2.join(); // wait for t2 to die
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
System.err.println("Join interrupted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PrinterRunnable implements Runnable {
|
||||||
|
char symbol;
|
||||||
|
int sleepTime;
|
||||||
|
|
||||||
|
public PrinterRunnable(char symbol, int sleepTime) {
|
||||||
|
this.symbol = symbol;
|
||||||
|
this.sleepTime = sleepTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
Thread current = Thread.currentThread();
|
||||||
|
System.out.println(current.getName() + " run started...");
|
||||||
|
for (int i = 1; i < 100; i++) {
|
||||||
|
System.out.print(symbol);
|
||||||
|
// c)
|
||||||
|
Thread.yield(); // release CPU and give other thread a chance
|
||||||
|
// try {
|
||||||
|
// Thread.sleep(sleepTime);
|
||||||
|
// } catch (InterruptedException e) {
|
||||||
|
// System.out.println(e.getMessage());
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
System.out.println('\n' + current.getName() + " run ended.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 121 KiB |
Binary file not shown.
After Width: | Height: | Size: 118 KiB |
|
@ -0,0 +1,355 @@
|
||||||
|
:source-highlighter: coderay
|
||||||
|
:icons: font
|
||||||
|
:experimental:
|
||||||
|
:!sectnums:
|
||||||
|
:imagesdir: ./images/
|
||||||
|
:handout: ../code/
|
||||||
|
|
||||||
|
:logo: IT.PROG2 -
|
||||||
|
ifdef::backend-html5[]
|
||||||
|
:logo: image:PROG2-300x300.png[IT.PROG2,100,100,role=right,fit=none,position=top right]
|
||||||
|
endif::[]
|
||||||
|
ifdef::backend-pdf[]
|
||||||
|
:logo:
|
||||||
|
endif::[]
|
||||||
|
ifdef::env-github[]
|
||||||
|
:tip-caption: :bulb:
|
||||||
|
:note-caption: :information_source:
|
||||||
|
:important-caption: :heavy_exclamation_mark:
|
||||||
|
:caution-caption: :fire:
|
||||||
|
:warning-caption: :warning:
|
||||||
|
endif::[]
|
||||||
|
|
||||||
|
= {logo} Lösungen zu den Übungen Concurrency–Execution
|
||||||
|
|
||||||
|
:sectnums:
|
||||||
|
:sectnumlevels: 2
|
||||||
|
// Beginn des Aufgabenblocks
|
||||||
|
|
||||||
|
== Concurrency 1 -- Java Threads
|
||||||
|
|
||||||
|
=== Theoretische Fragen [TU]
|
||||||
|
|
||||||
|
[loweralpha]
|
||||||
|
. 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.
|
||||||
|
+
|
||||||
|
[cols="a"]
|
||||||
|
|===
|
||||||
|
|Variante 1 (Von Thread ableiten)::
|
||||||
|
Für den parallel laufenden Code wird eine Klasse erstellt, welche von `java.lang.Thread` abgeleitet ist und die run() Methode überschrieben. Zum Starten kann diese direkt erzeugt und gestartet (`start()`) werden.
|
||||||
|
+
|
||||||
|
[source, Java]
|
||||||
|
----
|
||||||
|
class MyThread extends Thread {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// do something
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thread objekt wird direkt erzeugt und gestartet
|
||||||
|
Thread thread = new MyThread();
|
||||||
|
thread.start();
|
||||||
|
----
|
||||||
|
Variante 2 (Implementieren von Runnable)::
|
||||||
|
Für den parallel laufenden Code wird eine Klasse erstellt, welche das Interface `java.lang.Runnable` erfüllt und die `run()` Methode implementiert. Zum Starten wird der Thread mit einer Instanz der Klasse initialisiert, deren `run()` Methode ausgeführt werden soll.
|
||||||
|
+
|
||||||
|
[source, Java]
|
||||||
|
----
|
||||||
|
class MyRunnable implements Runnable {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// do something
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Das Thread Objekt wird mit einem Runnable initialisiert und gestartet
|
||||||
|
Thread thread = new Thread(new MyRunnable());
|
||||||
|
thread.start();
|
||||||
|
----
|
||||||
|
Vorteil: Die Klasse könnte noch von einer anderen (eigenen) Klasse abgeleitet sein.
|
||||||
|
|===
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
. 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.
|
||||||
|
+
|
||||||
|
[cols="a"]
|
||||||
|
|===
|
||||||
|
|NEW:: Thread wurde mit `new` erzeugt, aber noch nicht gestartet.
|
||||||
|
RUNNABLE:: Thread wurde gestartet und wird von der JVM ausgeführt. Die zwei Unterzustände zeigen, ob ein Thread im Moment vom Scheduler aktiviert wurde:
|
||||||
|
* *Ready*:
|
||||||
|
Thread ist bereit zu arbeiten, wartet aber noch auf die CPU, welche ggf. durch einen anderen Thread belegt ist.
|
||||||
|
* *Running*:
|
||||||
|
Der Thread wurde vom Scheduler einer CPU zugewiesen und arbeitet
|
||||||
|
Suspended::
|
||||||
|
Der Thread ist aus diversen Gründen unterbrochen:
|
||||||
|
* *BLOCKED*:
|
||||||
|
Warten auf monitor bzw. mutex lock
|
||||||
|
* *WAITING*:
|
||||||
|
Warten auf eine Condition (`wait()` resp. `await()`) oder andere blockierende calls (`join()`)
|
||||||
|
* *TIMED_WAITING*:
|
||||||
|
Warten auf blockierende Calls mit timeout (`sleep()`, `join(time)`, `wait(time)`)
|
||||||
|
|
||||||
|
TERMINATED:: Thread wurde beendet (Rückkehr aus `run()`), jedoch ist die Instanz immer noch vorhanden. Sie kann nicht wiederverwendet werden.
|
||||||
|
|
||||||
|
|
||||||
|
.Thread Zustandsmodell Lösung (vereinfacht, mit Übergängen)
|
||||||
|
image::Thread-State-Model-sol.png[pdfwidth=80%, width=900px]
|
||||||
|
|===
|
||||||
|
|
||||||
|
=== Printer-Threads: Verwendung von Java Threads [PU]
|
||||||
|
|
||||||
|
Nachfolgend einige Basisübungen zum Starten und Stoppen von Threads in Java.
|
||||||
|
|
||||||
|
[source, 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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
[loweralpha]
|
||||||
|
. 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.
|
||||||
|
+
|
||||||
|
[cols="a"]
|
||||||
|
|===
|
||||||
|
|Siehe Code: `PrinterLsgA`
|
||||||
|
|
||||||
|
`Thread.currentThread()` liefert den aktuellen Thread zurück. Wenn die aktuelle Instanz (`this`) nicht mit dem aktuellen Thread übereinstimmt, dann läuft `run()` in einem anderen Thread und wurde direkt aufgerufen.
|
||||||
|
*Achtung*: Vergleichen Sie nicht den Namen, da dieser beim Erzeugen beliebig gesetzt werden kann und auch nicht verhindert wird, dass ein Name doppelt vorkommt.
|
||||||
|
|===
|
||||||
|
|
||||||
|
. 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).
|
||||||
|
+
|
||||||
|
[cols="a"]
|
||||||
|
|===
|
||||||
|
|Siehe Code: `PrinterLsgB`
|
||||||
|
|===
|
||||||
|
|
||||||
|
. 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?
|
||||||
|
+
|
||||||
|
[cols="a"]
|
||||||
|
|===
|
||||||
|
|Siehe Code: `PrinterLsgCD`
|
||||||
|
|
||||||
|
Mit `yield()` kann dem Scheduler signalisiert werden, dass der Thread die CPU abgeben möchte.
|
||||||
|
Der Scheduler kann, muss das Angebot jedoch nicht annehmen.
|
||||||
|
Im Gegensatz zu `pause()` (mit time > 0) welches den Thread immer pausiert, wird ein Wechsel somit nicht erzwungen. Man überlässt dem Scheduler die Entscheidung.
|
||||||
|
Normalerweise finden dann weniger Thread-Wechsel statt. +
|
||||||
|
Inwieweit das "fairer" ist, kann diskutiert werden.
|
||||||
|
|===
|
||||||
|
|
||||||
|
. Wie muss man das Hauptprogramm anpassen, damit der Main-Thread immer
|
||||||
|
als letztes endet?
|
||||||
|
+
|
||||||
|
[cols="a"]
|
||||||
|
|===
|
||||||
|
|Siehe Code: `PrinterLsgCD`
|
||||||
|
|
||||||
|
Mit `join()` kann auf einen bestimmten Thread gewartet werden.
|
||||||
|
Falls der Thread bereits beendet ist, kehrt die Funktion sofort zurück.
|
||||||
|
Wenn man nur darauf wartet, dass alle Threads beendet sind, spielt die Reihenfolge der `join()` Befehle keine Rolle.
|
||||||
|
|===
|
||||||
|
|
||||||
|
|
||||||
|
== Concurrency 2 -- Executor Framework, Callables and Futures
|
||||||
|
|
||||||
|
=== 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.
|
||||||
|
|
||||||
|
[loweralpha]
|
||||||
|
. 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.
|
||||||
|
+
|
||||||
|
[cols="a"]
|
||||||
|
|===
|
||||||
|
|`CachedThreadPool`::
|
||||||
|
Bei steigener Benutzerzahl wird die Anzahl Threads automatisch erhöht. Sobald diese nicht mehr benötigt werden, werden sie wieder reduziert.
|
||||||
|
|===
|
||||||
|
|
||||||
|
. 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.
|
||||||
|
+
|
||||||
|
[cols="a"]
|
||||||
|
|===
|
||||||
|
|`FixedThreadPool` mit Grösse gleich Anzahl CPU-Cores::
|
||||||
|
CPU intensive Tasks können somit mit optimaler Geschwindigkeit ohne Unterbrechung (Thread-Switching) abgearbeitet werden.
|
||||||
|
|===
|
||||||
|
|
||||||
|
. 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.
|
||||||
|
+
|
||||||
|
[cols="a"]
|
||||||
|
|===
|
||||||
|
|`FixedThreadPool` mit Grösse gleich der Anzahl paralleler Algorithmen und Verwendung von `invokeAny`::
|
||||||
|
Die Algorithmen laufe parallel. Sobald der erste das Resultat liefert, werden die anderen automatisch beendet.
|
||||||
|
|===
|
||||||
|
|
||||||
|
=== Prime Checker [PU]
|
||||||
|
|
||||||
|
In dieser Aufgabe üben sie die Verwendung des Java Executor Frameworks zum Ausführen von mehreren unabhängigen Aufgaben (Tasks).
|
||||||
|
Mit der Wahl des Typs und der Konfiguration des ExecutorServices, bestimmen Sie auch ob und wie diese Tasks parallel d.h. in Threads ablaufen.
|
||||||
|
|
||||||
|
Im link:{handout}[Praktikumsverzeichnis] finden sie das Modul `PrimeChecker`.
|
||||||
|
Die Anwendung testet für eine Menge an zufälligen grossen Zahlen, ob es sich dabei um eine Primzahl handelt, indem es Brute-Force nach dem kleinstmöglichen Faktor (>1) sucht, durch den die Zahl ganzzahlig geteilt werden kann.
|
||||||
|
|
||||||
|
Die Klasse 'PrimeChecker' enthält die Hauptanwendung in einer Schleife zufällige Zahlen erzeugt und testet.
|
||||||
|
Die Verifizierung, ob es sich um eine Primzahl handelt, ist in die Klasse `PrimeTask` ausgelagert, welche bereits `Runnable` implementiert.
|
||||||
|
In der ausgelieferten Form wird jedoch alles im `main`-Thread ausgeführt.
|
||||||
|
|
||||||
|
[loweralpha]
|
||||||
|
. Studieren und testen Sie `PrimeChecker`. +
|
||||||
|
Wie lange dauert die Analyse der Zahlen aktuell?
|
||||||
|
+
|
||||||
|
[cols="a"]
|
||||||
|
|===
|
||||||
|
|Die Laufzeit wird am Ende ausgegeben. Je nach Zufallszahl und Rechnerausstattung variert die Zeit. Sie sollte jedoch im Bereich von rund einer halben Minute liegen.
|
||||||
|
|===
|
||||||
|
. Erweitern Sie `PrimeChecker` damit für jede Analyse (`PrimeTask`-Instanz) ein eigener Thread gestartet wird. +
|
||||||
|
[arabic]
|
||||||
|
.. Wie lange dauert die Analyse jetzt?
|
||||||
|
+
|
||||||
|
[cols="a"]
|
||||||
|
|===
|
||||||
|
| Siehe Musterlösung Klasse `PrimeChecker` +
|
||||||
|
Damit sie die Laufzeit korrekt messen können müssen Sie die Threads zwischenspeichern und am Schluss warten bis alle beendet sind → `join()`. +
|
||||||
|
Die Laufzeit sollte sich stark reduzieren, auf zirka ein Drittel der Zeit.
|
||||||
|
|===
|
||||||
|
.. Wie viele Threads werden gestartet?
|
||||||
|
+
|
||||||
|
[cols="a"]
|
||||||
|
|===
|
||||||
|
| Für die Berechnung der Primzahlen werden so viele Threads gestartet wie sie Primzahlen berechnen; also 500.
|
||||||
|
|===
|
||||||
|
|
||||||
|
Im nächsten Schritt soll für das Ausführen der `PrimeTask`-Instanzen ein ExecutorService verwendet werden.
|
||||||
|
|
||||||
|
[loweralpha, start=3]
|
||||||
|
. Ergänzen Sie die Klasse `PrimeCheckerExecutor` so, dass für das Thread-Management jetzt vom ExecutorService erledigt wird.
|
||||||
|
Als Unterstützung sind entsprechende `TODO:` Komentare enthalten.
|
||||||
|
[arabic]
|
||||||
|
.. Welche(r) Thread-Pool-Typ(en) eignet sich für diese Aufgabe?
|
||||||
|
+
|
||||||
|
[cols="a"]
|
||||||
|
|===
|
||||||
|
| Da es sich um CPU-intensive Tasks handelt, eignen sich Pools mit einer fixen Grösse.
|
||||||
|
Am Besten ein FixedThreadPool.
|
||||||
|
|===
|
||||||
|
.. Wie gross sollte der Thread-Pool sein um das beste Ergebnis zu erzeugen? +
|
||||||
|
Testen Sie mit unterschiedlichen Pool-Typen und Grössen.
|
||||||
|
+
|
||||||
|
[cols="a"]
|
||||||
|
|===
|
||||||
|
| Die Anzahl Threads sollte die Menge der CPU-Kerne nicht überschreiten, je nachdem, ob noch anderen CPU-intensiven Workloads auf dem Rechner laufen. +
|
||||||
|
Die Anzahl Kerne können sie von der Laufzeitumgebung abfragen mit: +
|
||||||
|
`Runtime.getRuntime().availableProcessors();` +
|
||||||
|
Beim `FixedThreadPool`, wird automatisch die Anzahl CPU-Kerne verwendet, wenn nichts angegeben wird.
|
||||||
|
|===
|
||||||
|
. Stellen Sie sicher, dass der `ExecutorService` am Schluss korrekt heruntergefahren wird.
|
||||||
|
+
|
||||||
|
[cols="a"]
|
||||||
|
|===
|
||||||
|
| Nach dem Auslösen des Herunterfahrens mit `shutdown()`, muss der Main-Thread warten, bis alle Tasks im Pool abgearbeitet sind. Dies kann mit der Methode
|
||||||
|
`executor.awaitTermination(_long timeout_, _TimeUnit unit_ )` erreicht werden. Wählen Sie den Timeout lang genug (1 Minute sollte reichen);
|
||||||
|
|===
|
||||||
|
[arabic]
|
||||||
|
.. Wie viele Threads werden jetzt gestartet?
|
||||||
|
+
|
||||||
|
[cols="a"]
|
||||||
|
|===
|
||||||
|
| Bei einem `FixedThreadPool` werden für die Berechnung so viele Threads gestartet, wie sie konfiguriert haben. Wird nichts angegeben, wird die Anzahl CPU-Kerne verwendet +
|
||||||
|
Bei einem `CachedThreadPool` hängt es stark davon ab, wie viele Threads gleichzeitig laufen. In jedem Fall wird es weniger sein (Faktor ~= Anzahl CPU-Kerne) als wenn jeder Task im eigenen Thread gestartet wird.
|
||||||
|
|===
|
||||||
|
.. Was sehen sie bei den Laufzeiten?
|
||||||
|
+
|
||||||
|
[cols="a"]
|
||||||
|
|===
|
||||||
|
| Die Laufzeiten liege alle relativ nahe beieinander. Die Varianz, welche durch die wechselnde Anzahl Primzahlen des zufälligen Samples generiert wird, ist höher als der Unterschied zwischen den verschiedenen Executors. Man müsste ein fixes oder umfangreicheres Sample verwenden, um grössere Unterschiede festzustellen.
|
||||||
|
|===
|
||||||
|
|
||||||
|
Im Moment wird das Resultat nur auf der Konsole ausgegeben, da `Runnable` kein Resultat zurückgeben können.
|
||||||
|
Im nächsten Schritt soll die Anwendung so umgebaut werden, dass die Berechnung in einem Callable passiert und das Resultat im Hauptprogramm verarbeitet (in unserem Fall nur ausgegeben) wird.
|
||||||
|
|
||||||
|
[loweralpha, start=5]
|
||||||
|
. Ergänzen Sie die Klasse `PrimeTaskCallable` so, dass das Resultat der Berechnung zurückgegeben wird. +
|
||||||
|
Da die Berechnung asynchron erfolgt, können Sie im Hauptprogramm das Resultat nicht mehr so einfach der Zahl zuordnen, für welche die Berechnung gestartet wurde. Deshalb muss im Resultat neben dem Faktor auch die zugehörige Zahl enthalten sein. Dazu können Sie die innere statische Klasse `PrimeTaskCallable.Result` verwenden.
|
||||||
|
+
|
||||||
|
[cols="a"]
|
||||||
|
|===
|
||||||
|
| Siehe Musterlösung Klasse `PrimeTaskCallable`. +
|
||||||
|
Stellen Sie sicher, dass sie den generischen Rückgabe-Typ des Callable deklarieren. +
|
||||||
|
`implements Callable<PrimeTaskCallable.Result>`
|
||||||
|
|===
|
||||||
|
. Vervollständigen sie das Hauptprogramm in der Klasse `PrimeCheckerFuture`, welches nun `PrimeTaskCallable` verwenden soll. +
|
||||||
|
Das Resultat soll, wie bei `PrimeChecker`, auf der Konsole ausgegeben werden. Jetzt jedoch im Hauptprogramm.
|
||||||
|
[TIP]
|
||||||
|
Beachten Sie, dass das Übermitteln des Tasks an den `ExecutorService` unmittelbar ein Objekt vom Typ `Future` zurückgeliefert, in welchem das Resultat nach Beendigung des Tasks abgelegt wird. +
|
||||||
|
Um auf das Resultat zuzugreifen, ohne die Übermittlung des nächsten Tasks zu blockieren, müssen sie dieses `Future`-Objekt zwischenspeichern (z.B. in einer Liste). +
|
||||||
|
Später können sie die Resultate aus der Liste durchgehen und weiterverarbeiten, was in unserem Fall die Ausgabe auf der Konsole ist.
|
||||||
|
+
|
||||||
|
[cols="a"]
|
||||||
|
|===
|
||||||
|
| Siehe Musterlösung Klasse `PrimeTaskCallable`. +
|
||||||
|
Stellen Sie sicher, dass sie den generischen (Rückgabe-)Typ des Callable deklarieren ->
|
||||||
|
`implements Callable<PrimeTaskCallable.Result>`
|
||||||
|
|===
|
||||||
|
|
||||||
|
. Merken Sie einen Unterschied in den Berechnungszeiten oder im Verhalten der Ausgabe? Wenn ja, warum könnte das so sein?
|
||||||
|
+
|
||||||
|
[cols="a"]
|
||||||
|
|===
|
||||||
|
| Die Rechenzeiten beim `Callable` sind etwa gleich lang, wie beim `Executor`. Jedoch werden die Resultate jetzt in der Reihenfolge der generierten Primzahlen ausgegeben, da die Liste bei der Ausgabe sequentiell abgearbeitet wird. +
|
||||||
|
Beim `Executor` wurde das Resultat eher nach Rechenzeit (kurze am Anfang) sortiert.
|
||||||
|
Da für Primzahlen praktisch bis zum halben Wert alle Zahlen überprüft werden, dauern die Prüfung länger und die Primzahlen erscheinen eher gegen Schluss.
|
||||||
|
|===
|
||||||
|
|
||||||
|
== Bewertete Pflichtaufgaben
|
||||||
|
|
||||||
|
=== Mandelbrot [PA]
|
||||||
|
****
|
||||||
|
Die Lösungen zu den bewerteten Pflichtaufgaben erhalten Sie nach der Abgabe und Bewertung aller Klassen.
|
||||||
|
****
|
||||||
|
// Ende des Aufgabenblocks
|
||||||
|
:!sectnums:
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue