Initial commit
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
+41
@@ -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));
|
||||
}
|
||||
}
|
||||
+53
@@ -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));
|
||||
}
|
||||
}
|
||||
+55
@@ -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));
|
||||
}
|
||||
}
|
||||
+32
@@ -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
|
||||
}
|
||||
}
|
||||
+50
@@ -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
Reference in New Issue
Block a user