Initial commit

This commit is contained in:
github-classroom[bot] 2022-03-31 09:49:56 +00:00
commit 5a1de0b8e7
83 changed files with 17003 additions and 0 deletions

29
.editorconfig Normal file
View File

@ -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

5
.gitattributes vendored Normal file
View File

@ -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

14
.github/classroom/autograding.json vendored Normal file
View File

@ -0,0 +1,14 @@
{
"tests": [
{
"name": "Run PA unit tests",
"setup": "",
"run": "gradle :Philosopher:test --info",
"input": "",
"output": "",
"comparison": "included",
"timeout": 10,
"points": 2
}
]
}

15
.github/workflows/classroom.yml vendored Normal file
View File

@ -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

70
.gitignore vendored Normal file
View File

@ -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

356
README.adoc Normal file
View File

@ -0,0 +1,356 @@
: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 - Cooperation
== Einleitung
Ziele dieses Praktikums sind:
* Sie können die verschiedenen Mechanismen zur Thread-Synchronisation sicher anwenden (Mutual-Exclusion, Condition-Synchronisation).
* Sie können das Monitor-Konzept (wait/notify, Lock & Conditions) in Java anwenden.
* Sie können Producer-Consumer Synchronisation praktisch anwenden.
* Sie wissen wie Deadlocks praktisch verhindert werden können.
=== Voraussetzungen
* Vorlesung Concurrency 1 bis 4
=== Tooling
* Installiertes JDK 17+
* Gradle 7.4+
=== 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 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}[Praktikumsverzeichnis Quellcode, Projektstruktur]
:sectnums:
:sectnumlevels: 2
// Beginn des Aufgabenblocks
== Concurrency 3 -- Thread Synchronisation
=== Konto-Übertrag [PU]
Im link:{handout}[Praktikumsverzeichnis] finden sie das Modul `AccountTransfer`.
Die Klasse `Account`, welche sie schon aus dem Unterricht kennen, repräsentiert ein Konto von welchem bzw. auf welches man Geld transferieren und den Kontostand abfragen kann.
Die Klasse `AccountTransferTask` ist ein `Runnable` welches einen Geldbetrag zwischen zwei Konten transferiert . Der entsprechende Ablauf ist in der Methode `accountTransfer` umgesetzt.
Um die Funktionalität zu verifizieren, wurde in der Klasse `AccountTransferSimulation` ein Testszenario umgesetzt,
in welchem eine Grosszahl kleiner Beträge zwischen drei Konten transferiert wird.
Dazu wird ein ExecutorService mit mehreren Threads verwendet, an welchen die TransferTasks übermittelt werden.
Die Simulation gibt die Kontostände und das Vermögen (Summe der Kontostände) zu Beginn und am Ende des Transfers aus.
[loweralpha]
. Was stellen Sie fest, wenn Sie die Simulation laufen lassen?
Erklären Sie wie die Abweichungen zustande kommen.
. Im Unterricht haben Sie gelernt, dass kritische Bereiche Ihres Codes durch Mutual-Exclusion geschützt werden sollen.
Wie macht man das in Java?
+
Versuchen Sie mittels von Mutual-Exclusion sicherzustellen, dass keine Abweichungen entstehen.
+
** Reicht es die kritischen Bereiche in Account zu sichern?
** Welches sind die kritischen Bereiche?
+
Untersuchen Sie mehrere Varianten von Locks (Lock auf Methode oder Block,
Lock auf Instanz oder Klasse).
+
Ihre Implementierung muss noch nebenläufige Transaktionen erlauben, d.h. wenn
Sie zu stark synchronisieren, werden alle Transaktionen in Serie ausgeführt und
Threads ergeben keinen Sinn mehr.
+
Stellen Sie für sich folgende Fragen:
+
** Welches ist das Monitor-Objekt?
** Braucht es eventuell das Lock von mehr als einem Monitor, damit eine Transaktion ungestört ablaufen kann?
. Wenn Sie es geschafft haben die Transaktion thread-safe zu implementieren,
ersetzen Sie in `AccountTransferSimulation` die folgende Zeile: +
`AccountTransferTask task1 = new AccountTransferTask(account**3**, account**1**, 2);` +
durch +
`AccountTransferTask task1 = new AccountTransferTask(account**1**, account**3**, 2);` +
und starten Sie das Programm noch einmal. Was stellen Sie fest?
(evtl. müssen Sie mehrfach neu starten, damit der Effekt auftritt). +
Was könnte die Ursache sein und wie können Sie es beheben? +
[NOTE]
Falls Sie die Frage noch nicht beantworten können, kommen sie nach der Vorlesung "Concurrency 4" nochmals auf diese Aufgabe zurück und versuchen Sie sie dann zu lösen.
=== Traffic Light [PU]
In dieser Aufgabe sollen Sie die Funktionsweise einer Ampel und deren Nutzung nachahmen.
Benutzen Sie hierzu die Vorgabe im Modul `TrafficLight`.
[loweralpha]
. Ergänzen Sie zunächst in der Klasse `TrafficLight` drei Methoden:
* Eine Methode zum Setzen der Ampel auf "rot".
* Eine Methode zum Setzen der Ampel auf "grün".
* Eine Methode mit dem Namen `passby()`. Diese Methode soll das Vorbeifahren
eines Fahrzeugs an dieser Ampel nachbilden: Ist die Ampel rot, so wird der
aufrufende Thread angehalten, und zwar so lange, bis die Ampel grün wird.
Ist die Ampel dagegen grün, so kann der Thread sofort aus der Methode zurückkehren,
ohne den Zustand der Ampel zu verändern. Verwenden Sie `wait`, `notify` und
`notifyAll` nur an den unbedingt nötigen Stellen!
+
[NOTE]
Die Zwischenphase „gelb“ spielt keine Rolle Sie können diesem Zustand ignorieren!
. Erweitern Sie nun die Klasse `Car` (abgeleitet von `Thread`). +
Im Konstruktor wird eine Referenz auf ein Feld von Ampeln übergeben.
Diese Referenz wird in einem entsprechenden Attribut der Klasse `Car` gespeichert.
In der `run`-Methode werden alle Ampeln dieses Feldes passiert, und zwar in einer Endlosschleife (d.h. nach dem Passieren der letzten Ampel des Feldes wird wieder die erste Ampel im Feld passiert). +
Natürlich darf das Auto erst dann eine Ampel passieren, wenn diese auf grün ist! +
Für die Simulation der Zeitspanne für das Passieren des Signals sollten Sie folgende Anweisung verwenden: `sleep\((int)(Math.random() * 500));`
Beantworten Sie entweder (c) oder (d) (nicht beide):
[loweralpha, start=3]
. Falls Sie bei der Implementierung der Klasse TrafficLight die Methode
`notifyAll()` benutzt haben: +
Hätten Sie statt `notifyAll` auch die Methode `notify` verwenden können, oder haben Sie `notifyAll()` unbedingt gebraucht?
Begründen Sie Ihre Antwort!
. Falls Sie bei der Implementierung der Klasse Ampel die Methode `notify()` benutzt
haben: +
Begründen Sie, warum `notifyAll()` nicht unbedingt benötigt wird!
. Testen Sie das Programm `TrafficLightOperation.java`.
Die vorgegebene Klasse implementiert eine primitive Simulation von Autos, welche die Ampeln passieren.
Studieren Sie den Code dieser Klasse und überprüfen Sie, ob die erzeugte Ausgabe sinnvoll ist.
=== Producer-Consumer Problem [PU]
In dieser Aufgabe wird ein Producer-Consumer Beispiel mittels einer Queue umgesetzt.
Dazu wird eine Implementation mittels eines link:https://en.wikipedia.org/wiki/Circular_buffer[Digitalen Ringspeichers] umgesetzt.
.Circular Buffer [Wikipedia]
[link = https://en.wikipedia.org/wiki/Circular_buffer]
image::Circular_Buffer_Animation.gif[pdfwidth=75%, width=600px]
Hierzu sind zwei Klassen (`CircularBuffer.java`, `Buffer.java`) vorgegeben, mit folgendem Design:
.Circular Buffer Design
image::CircularBuffer.png[pdfwidth=75%, width=600px]
==== Analyse der abgegebenen CircularBuffer Umsetzung.
Mit dem Testprogramm `CircBufferSimulation` kann die Funktion der `CircularBuffer` Implementierung analysiert werden.
Der mitgelieferte `Producer`-Thread generiert kontinuierlich Daten (in unserem Fall aufsteigende Nummern) und füllt diese mit `buffer.put(...)` in den Buffer.
Der `Consumer`-Thread liest die Daten kontinuierlich mit `buffer.get()` aus dem Buffer aus.
Beide Threads benötigen eine gewisse Zeit zum Produzieren bzw. Konsumieren der Daten.
Diese kann über die Variablen `maxProduceTime` bzw. `maxConsumeTime` beeinflusst werden.
Es können zudem mehrere Producer- bzw. Consumer-Threads erzeugt werden.
[loweralpha]
. Starten Sie `CircularBufferSimulation` und analysieren Sie die Ausgabe.
Der Status des Buffers (belegte Positionen und Füllstand) wird sekündlich ausgegeben.
Alle fünf Sekunden wird zudem der aktuelle Inhalt des Buffers ausgegeben. +
** Wie ist das Verhalten des `CircularBuffer` bei den Standard-Testeinstellungen?
. Analysieren Sie die Standard-Einstellungen in `CircularBufferSimulation`.
** Welche Varianten gibt es, die Extrempositionen (Buffer leer, bzw. Buffer voll) zu erzeugen?
** Was ist das erwartete Verhalten bei vollem bzw. leerem Buffer? (bei Producer bzw. Consumer)
. Testen Sie was passiert, wenn der Buffer an die Kapazitätsgrenze kommt. Passen Sie dazu die Einstellungen in `CircularBufferSimulation` entsprechend an. +
[TIP]
Belassen sie die Anzahl Producer-Threads vorerst auf 1, damit der Inhalt des Buffers (Zahlenfolge) einfacher verifiziert werden kann.
+
** Was Stellen Sie fest? Was passiert wenn der Buffer voll ist? Warum?
. Wiederholen Sie das Gleiche für einen leeren Buffer. Passen Sie die Einstellungen so an, dass der Buffer sicher leer wird, d.h. der `Consumer` keine Daten vorfindet.
** Was stellen Sie fest, wenn der Buffer leer ist? Warum? +
[TIP]
Geben Sie gegebenenfalls die gelesenen Werte des Konsumenten-Threads aus.
==== Thread-Safe Circular Buffer
In der vorangehenden Übung griffen mehrere Threads (`Producer` & `Consumer`) auf den gleichen Buffer zu.
Die Klasse `CircularBuffer` ist aber nicht thread-safe.
Deshalb soll jetzt eine Wrapper Klasse geschrieben werden, welche die CircularBuffer-Klasse "thread-safe" macht.
Das führt zu folgendem Design:
.Guarded Circular Buffer Design
image::GuardedCircularBuffer.png[pdfwidth=75%, width=600px]
[NOTE]
====
Beachten Sie, dass es sich hier um einen Wrapper (keine Vererbung) handelt. +
Der `GuardedCircularBuffer` hält eine Referenz auf ein `CircularBuffer`-Objekt welches er im Hintergrund für die Speicherung verwendet. Das heißt, viele Methodenaufrufe werden einfach an das gekapselte Objekt weitergeleitet. Einzelne Methoden werden jedoch in ihrer Funktion erweitert. Man spricht auch von "Dekorieren" des Original-Objektes (siehe link:{decorator-pattern}[Decorator-Pattern]).
====
:decorator-pattern: https://en.wikipedia.org/wiki/Decorator_pattern
[loweralpha, start=5]
. Ergänzen Sie die vorhandene Klasse `GuardedCircularBuffer` sodass:
** Die Methoden `empty`, `full`, `count` das korrekte Resultat liefern.
** Aufrufe von `put` blockieren, solange der Buffer voll ist, d.h. bis mindestens wieder ein leeres Buffer-Element vorhanden ist.
** Analog dazu Aufrufe von `get` blockieren, solange der Buffer leer ist, d.h, bis mindestens ein Element im Buffer vorhanden ist.
[TIP]
====
Verwenden Sie den Java Monitor des `GuardedCircularBuffer`-Objektes!
Wenn die Klasse fertig implementiert ist, soll sie in der `CircBufferSimulation` Klasse verwendet werden.
====
Beantworten Sie entweder (i) oder (ii) (nicht beide):
[lowerroman]
. Falls Sie bei der Implementierung der Klasse `GuardedCircularBuffer` die Methode `notifyAll()` benutzt haben:
Hätten Sie statt `notifyAll()` auch die Methode `notify()` verwenden können oder haben Sie `notifyAll()` unbedingt
benötigt? Begründen Sie Ihre Antwort!
. Falls Sie bei der Implementierung der Klasse `GuardedCircularBuffer` die Methode `notify()` benutzt haben:
Begründen Sie, warum Sie `notifyAll()` nicht unbedingt benötigten!
== Concurrency 4 -- Lock & Conditions, Deadlocks
=== Single-Lane Bridge [PU]
Die Brücke über einen Fluss ist so schmal, dass Fahrzeuge nicht kreuzen können.
Sie soll jedoch von beiden Seiten überquert werden können.
Es braucht somit eine Synchronisation, damit die Fahrzeuge nicht kollidieren.
Um das Problem zu illustrieren wird eine fehlerhaft funktionierende Anwendung,
in welcher keine Synchronisierung vorgenommen wird, zur Verfügung gestellt.
Ihre Aufgabe ist es, die Synchronisation der Fahrzeuge einzubauen.
Die Anwendung finden Sie im link:{handout}[Praktikumsverzeichnis] im Modul `Bridge`.
Gestartet wird sie indem die Klasse `Main` ausgeführt wird (z.B. mit `gradle run`).
Das GUI sollte selbsterklärend sein.
Mit den zwei Buttons können sie Autos links bzw. rechts hinzufügen. Sie werden feststellen, dass die Autos auf der Brücke kollidieren, bzw. illegalerweise durcheinander hindurchfahren.
.Single-Lane Bridge GUI
image::bridge_overview.png[pdfwidth=75%, width=600px]
Um das Problem zu lösen, müssen Sie die den GUI Teil der Anwendung nicht verstehen.
Sie müssen nur wissen, dass Fahrzeuge, die von links nach rechts fahren, die Methode `controller.enterLeft()` aufrufen bevor sie auf die Brücke fahren (um Erlaubnis fragen) und die Methode `controller.leaveRight()` aufrufen, sobald sie die Brücke verlassen.
Fahrzeuge in die andere Richtung rufen entsprechend die Methoden `enterRight()` und `leaveLeft()` auf.
Dabei ist `controller` eine Instanz der Klasse `TrafficController`, welche für die Synchronisation zuständig ist.
In der mitgelieferten Klasse sind die vier Methoden nicht implementiert (Dummy-Methoden).
[loweralpha]
. Erweitern sie `TrafficController` zu einer Monitorklasse, die sicherstellt, dass die Autos nicht mehr kollidieren.
Verwenden Sie dazu den Lock und Conditions Mechanismus.
[TIP]
Verwenden Sie eine Statusvariable, um den Zustand der Brücke zu repräsentieren (e.g. `boolean bridgeOccupied`).
. Passen Sie die Klasse `TrafficController` so an, dass jeweils abwechslungsweise ein Fahrzeug von links und rechts die Brücke passieren kann.
Unter Umständen wird ein Auto blockiert, wenn auf der anderen Seite keines mehr wartet.
Verwenden Sie für die Lösung mehrere Condition Objekte.
[NOTE]
Um die Version aus a. zu behalten, können sie auch eine Kopie (z.B. `TrafficControllerB`) erzeugen und `Main` anpassen, damit eine Instanz dieser Klasse verwendet wird.
. Bauen Sie die Klasse `TrafficController` so um, dass jeweils alle wartenden Fahrzeuge aus einer Richtung passieren können.
Erst wenn keines mehr wartet, kann die Gegenrichtung fahren.
[TIP]
Mit link:{ReentrantLock}[`ReentrentLock.hasWaiters(Condition c)`] können Sie
abfragen ob Threads auf eine bestimmte Condition warten.
:ReentrantLock: https://docs.oracle.com/en/java/javase/16/docs/api/java.base/java/util/concurrent/locks/ReentrantLock.html#hasWaiters(java.util.concurrent.locks.Condition)
=== The Dining Philosophers [PA]
.**Beschreibung des Philosophen-Problems:**
****
Fünf Philosophen sitzen an einem Tisch mit einer Schüssel, die immer genügend Spaghetti enthält.
Ein Philosoph ist entweder am Denken oder am Essen.
Um zu essen braucht er zwei Gabeln.
Es hat aber nur fünf Gabeln.
Ein Philosoph kann zum Essen nur die neben ihm liegenden Gabeln gebrauchen.
Aus diesen Gründen muss ein Philosoph warten und hungern, solange einer seiner Nachbarn am Essen ist.
****
[.float-group]
--
[.left]
.Philosopher UI
image::philosopher-ui.png[pdfwidth=30%, width=400px, role="left"]
--
Das Bild zeigt die Ausgabe des Systems, das wir in dieser Aufgabe verwenden.
Die blauen Kreise stellen denkende Philosophen dar, die gelben essende und die roten hungernde.
Bitte beachten Sie, dass eine Gabel, die im Besitz eines Philosophen ist, zu dessen Teller hin verschoben dargestellt ist.
Die Anwendung besteht aus drei Dateien / Hauptklassen (jeweils mit zusätzlichen inneren Klassen):
`PhilosopherGui`:: Ist das Hauptprogramm und repräsentiert das GUI (Java-Swing basiert).
Die Klasse initialisiert die Umgebung `PhilosopherTable`, startet die Simulation und erzeugt die obige Ausgabe.
Zudem werden Statusmeldungen der Philosophen auf der Konsole ausgegeben.
`PhilosopherTable`:: Repräsentiert das Datenmodell. Sie initialisiert, startet und stoppt die Threads der Klasse `Philosopher`,
welche das Verhalten und Zustände (THINKING, HUNGRY, EATING) der Philosophen abbildet und als innere Klasse umgesetzt ist.
`ForkManager`:: Diese Klasse enthält die Strategie, wie die Philosophen die zwei Gabeln (Klasse `Fork`)
aufnehmen (`aquireForks(int philosopherId)`) und zurücklegen (`releaseForks(int philosopherId)`).
[loweralpha]
. Analysieren Sie die bestehende Lösung (vor allem `ForkManager`), die bekanntlich nicht Deadlock-frei ist.
Kompilieren und starten Sie die Anwendung.
Nach einiger Zeit geraten die Philosophen in eine Deadlock-Situation und verhungern.
Überlegen Sie sich, wo im Code der Deadlock entsteht.
. Passen Sie die bestehende Lösung so an, dass keine Deadlocks mehr möglich sind.
Im Unterricht haben Sie mehrere Lösungsansätze kennengelernt. +
In der umzusetzenden Lösung soll der `ForkManager` immer das Gabelpaar eines Philosophen in einer _atomaren_ Operation
belegen bzw. freigeben und nicht die einzelnen Gabeln sequentiell nacheinander.
Dazu müssen beide Gabeln (links & rechts) auch verfügbar (`state == FREE`) sein, ansonsten muss man warten, bis beide verfügbar sind.
** Es sind nur Anpassungen in der Datei `ForkManager.java` notwendig.
Die `PhilosopherGui` und `PhilosopherTable`-Klassen müssen nicht angepasst werden.
** Verändern Sie nicht das `public` interface des `ForkManager`
`acquireForks(int philosopherId)` und `releaseForks(int philosopherId)` müssen bestehen bleiben und verwendet werden.
** Verwenden Sie für die Synchronisation Locks und Conditions!
// Ende des Aufgabenblocks
:!sectnums:
// == Aufräumarbeiten
== Abschluss
Stellen Sie sicher, dass die Pflichtaufgaben mittels `gradle run` gestartet werden können und die Tests mit `gradle test` erfolgreich laufen und pushen Sie die Lösung vor der Deadline in Ihr Abgaberepository.

View File

@ -0,0 +1,22 @@
/*
* Gradle build configuration for specific lab module / exercise
* Default declarations can be found in the lab main build configuration (../../gradle.build)
* Declarations in this file extend or override the default values.
*/
// the Java plugin is added by default in the main lab configuration
plugins {
// Apply the application plugin to add support for building a CLI application.
id 'application'
}
description = 'Lab04 AccountTransfer'
dependencies {
}
// Configuration for Application plugin
application {
// Define the main class for the application.
mainClass = 'ch.zhaw.prog2.account.AccountTransferSimulation'
}

View File

@ -0,0 +1,23 @@
package ch.zhaw.prog2.account;
public class Account {
private final int id;
private int balance = 0;
public Account(int id, int initialAmount) {
this.id = id;
this.balance = initialAmount;
}
public int getId() {
return id;
}
public int getBalance() {
return balance;
}
public void transferAmount(int amount) {
this.balance += amount;
}
}

View File

@ -0,0 +1,66 @@
package ch.zhaw.prog2.account;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class AccountTransferSimulation {
private static final int ITERATIONS = 10000;
public static void main(String[] args) throws InterruptedException {
Account account1 = new Account(1, 10);
Account account2 = new Account(2, 10);
Account account3 = new Account(3, 999999);
System.out.println("Start Balance:");
System.out.println("- Account1 = " + account1.getBalance());
System.out.println("- Account2 = " + account2.getBalance());
System.out.println("- Account3 = " + account3.getBalance());
System.out.println("Summed up balances of all accounts: " +
(account1.getBalance() + account2.getBalance() + account3.getBalance()));
System.out.println("Start of Transaction");
AccountTransferTask task1 = new AccountTransferTask(account3, account1, 2);
AccountTransferTask task2 = new AccountTransferTask(account3, account2, 1);
AccountTransferTask task3 = new AccountTransferTask(account2, account1, 2);
// create ExecutorService
ExecutorService executor = Executors.newFixedThreadPool(8);
// execute transactions on accounts
System.out.println("Submitting...");
for (int count = 0; count < ITERATIONS; count++) {
executor.execute(task1);
executor.execute(task2);
executor.execute(task3);
}
System.out.println("Working...");
executor.shutdown();
// wait for completion
boolean completed = executor.awaitTermination(20, TimeUnit.SECONDS);
if (completed) {
System.out.println("Transactions completed!");
} else {
System.out.println("Transactions timed out!");
executor.shutdownNow();
}
// Print end statistics
System.out.println("Amount transferred (when enough on fromAccount):");
System.out.println("- Task 1 = " + task1.getTotalTransfer());
System.out.println("- Task 2 = " + task2.getTotalTransfer());
System.out.println("- Task 3 = " + task3.getTotalTransfer());
System.out.println("End Balance:");
System.out.println("- Account1 = " + account1.getBalance());
System.out.println("- Account2 = " + account2.getBalance());
System.out.println("- Account3 = " + account3.getBalance());
System.out.println("Summed up balances of all accounts (should be the same as at start): " +
(account1.getBalance() + account2.getBalance() + account3.getBalance()));
}
}

View File

@ -0,0 +1,33 @@
package ch.zhaw.prog2.account;
class AccountTransferTask implements Runnable {
private final Account fromAccount;
private final Account toAccount;
private final int amount;
private int totalTransfer = 0;
public AccountTransferTask(Account fromAccount, Account toAccount, int amount) {
this.fromAccount = fromAccount;
this.toAccount = toAccount;
this.amount = amount;
}
public int getTotalTransfer() {
return this.totalTransfer;
}
@Override
public void run() {
transfer();
}
private void transfer() {
// Account must not be overdrawn
if (fromAccount.getBalance() >= amount) {
fromAccount.transferAmount(-amount);
toAccount.transferAmount(amount);
totalTransfer += amount;
}
}
}

22
code/Bridge/build.gradle Normal file
View File

@ -0,0 +1,22 @@
/*
* Gradle build configuration for specific lab module / exercise
* Default declarations can be found in the lab main build configuration (../../gradle.build)
* Declarations in this file extend or override the default values.
*/
// the Java plugin is added by default in the main lab configuration
plugins {
// Apply the application plugin to add support for building a CLI application.
id 'application'
}
description = 'Lab04 Bridge'
dependencies {
}
// Configuration for Application plugin
application {
// Define the main class for the application.
mainClass = 'ch.zhaw.prog2.bridge.Main'
}

View File

@ -0,0 +1,82 @@
package ch.zhaw.prog2.bridge;
import java.awt.*;
public class Car implements Runnable {
public static final int REDCAR = 0;
public static final int BLUECAR = 1;
private final int bridgeY = 95;
private final int bridgeXLeft = 210;
private final int bridgeXLeft2 = 290;
private final int bridgeXMid = 410;
private final int bridgeXRight2 = 530;
private final int bridgeXRight = 610;
private final int totalWidth = 900;
private final int initX[] = {-80, totalWidth};
private final int initY[] = {135, 55};
private final int outLeft = -200;
private final int outRight = totalWidth + 200;
int cartype;
int xpos, ypos;
Car inFront;
Image image;
TrafficController controller;
public Car(int cartype, Car inFront, Image image, TrafficController controller) {
this.cartype = cartype;
this.inFront = inFront;
this.image = image;
this.controller = controller;
if (cartype == REDCAR) {
xpos = inFront == null ? outRight : Math.min(initX[cartype], inFront.getX()-90);
} else {
xpos = inFront == null ? outLeft : Math.max(initX[cartype], inFront.getX()+90);
}
ypos = initY[cartype];
}
public void move() {
int xposOld = xpos;
if (cartype==REDCAR) {
if (inFront.getX() - xpos > 100) {
xpos += 4;
if (xpos >= bridgeXLeft && xposOld < bridgeXLeft) controller.enterLeft();
else if (xpos > bridgeXLeft && xpos < bridgeXMid) { if (ypos > bridgeY) ypos -= 2; }
else if (xpos >= bridgeXRight2 && xpos < bridgeXRight) { if (ypos < initY[REDCAR]) ypos += 2; }
else if (xpos >= bridgeXRight && xposOld < bridgeXRight) controller.leaveRight();
}
} else {
if (xpos-inFront.getX() > 100) {
xpos -= 4;
if (xpos <= bridgeXRight && xposOld > bridgeXRight) controller.enterRight();
else if (xpos < bridgeXRight && xpos > bridgeXMid) { if (ypos < bridgeY) ypos += 2; }
else if (xpos <= bridgeXLeft2 && xpos > bridgeXLeft) { if(ypos > initY[BLUECAR]) ypos -= 2; }
else if (xpos <= bridgeXLeft && xposOld > bridgeXLeft) controller.leaveLeft();
}
}
}
public void run() {
boolean outOfSight = cartype == REDCAR ? xpos > totalWidth : xpos < -80;
while (!outOfSight) {
move();
outOfSight = cartype == REDCAR ? xpos > totalWidth : xpos < -80;
try {
Thread.sleep(30);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
xpos = cartype == REDCAR ? outRight: outLeft;
}
public int getX() { return xpos; }
public void draw(Graphics g) {
g.drawImage(image, xpos, ypos, null);
}
}

View File

@ -0,0 +1,25 @@
package ch.zhaw.prog2.bridge;
import javax.swing.*;
import java.awt.*;
public class CarWindow extends JFrame {
public CarWindow(CarWorld world) {
Container c = getContentPane();
c.setLayout(new BorderLayout());
c.add("Center",world);
JButton addLeft = new JButton("Add Left");
JButton addRight = new JButton("Add Right");
addLeft.addActionListener((e) -> world.addCar(Car.REDCAR));
addRight.addActionListener((e) -> world.addCar(Car.BLUECAR));
JPanel p1 = new JPanel();
p1.setLayout(new FlowLayout());
p1.add(addLeft);
p1.add(addRight);
c.add("South",p1);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
}

View File

@ -0,0 +1,66 @@
package ch.zhaw.prog2.bridge;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
class CarWorld extends JPanel {
Image bridge;
Image redCar;
Image blueCar;
TrafficController controller;
ArrayList<Car> blueCars = new ArrayList<>();
ArrayList<Car> redCars = new ArrayList<>();
public CarWorld(TrafficController controller) {
this.controller = controller;
MediaTracker mt = new MediaTracker(this);
Toolkit toolkit = Toolkit.getDefaultToolkit();
redCar = toolkit.getImage(getClass().getResource("redcar.gif"));
mt.addImage(redCar, 0);
blueCar = toolkit.getImage(getClass().getResource("bluecar.gif"));
mt.addImage(blueCar, 1);
bridge = toolkit.getImage(getClass().getResource("bridge1.gif"));
mt.addImage(bridge, 2);
try {
mt.waitForID(0);
mt.waitForID(1);
mt.waitForID(2);
} catch (java.lang.InterruptedException e) {
System.out.println("Couldn't load one of the images");
}
redCars.add( new Car(Car.REDCAR,null,redCar,null) );
blueCars.add( new Car(Car.BLUECAR,null,blueCar,null) );
setPreferredSize( new Dimension(bridge.getWidth(null), bridge.getHeight(null)) );
}
@Override
public void paintComponent(Graphics g) {
g.drawImage(bridge,0,0,this);
for (Car c : redCars) c.draw(g);
for (Car c : blueCars) c.draw(g);
}
public void addCar(final int cartype) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Car car;
if (cartype==Car.REDCAR) {
car = new Car(cartype,redCars.get(redCars.size()-1),redCar,controller);
redCars.add(car);
} else {
car = new Car(cartype,blueCars.get(blueCars.size()-1),blueCar,controller);
blueCars.add(car);
}
new Thread(car).start();
}
});
}
}

View File

@ -0,0 +1,31 @@
package ch.zhaw.prog2.bridge;
public class Main {
private static void nap(int ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
public static void main(String[] a) {
final TrafficController controller = new TrafficController();
final CarWorld world = new CarWorld(controller);
final CarWindow win = new CarWindow(world);
win.pack();
win.setVisible(true);
new Thread(new Runnable() {
public void run() {
while (true) {
nap(25);
win.repaint();
}
}
}).start();
}
}

View File

@ -0,0 +1,19 @@
package ch.zhaw.prog2.bridge;
/*
* Controls the traffic passing the bridge
*/
public class TrafficController {
/* Called when a car wants to enter the bridge form the left side */
public void enterLeft() {}
/* Called when a wants to enter the bridge form the right side */
public void enterRight() {}
/* Called when the car leaves the bridge on the left side */
public void leaveLeft() {}
/* Called when the car leaves the bridge on the right side */
public void leaveRight() {}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,63 @@
/*
* Gradle build configuration for specific lab module / exercise
* Default declarations can be found in the lab main build configuration (../../gradle.build)
* Declarations in this file extend or override the default values.
*/
// enabled plugins
plugins {
id 'java'
// Apply the application plugin to add support for building a CLI application.
id 'application'
}
// Project/Module information
description = 'Lab04 CircularBuffer'
group = 'ch.zhaw.prog2'
version = '2022.1'
// Dependency configuration
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.+'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.+'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.+'
testImplementation 'org.mockito:mockito-core:4.3.1'
}
// Test task configuration
test {
// Use JUnit platform for unit tests
useJUnitPlatform()
testLogging {
events "PASSED", "SKIPPED", "FAILED"
}
}
// Configuration for Application plugin
application {
// Define the main class for the application.
mainClass = 'ch.zhaw.prog2.circularbuffer.CircBufferSimulation'
}
// Required to allow System.in.read() when run with gradle.
run {
standardInput = System.in
}
// 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
}
}

View File

@ -0,0 +1,11 @@
package ch.zhaw.prog2.circularbuffer;
public interface Buffer<T> {
boolean put(T element) throws InterruptedException;
T get() throws InterruptedException;
boolean empty();
boolean full();
int count();
void printBufferSlots();
void printBufferContent();
}

View File

@ -0,0 +1,119 @@
package ch.zhaw.prog2.circularbuffer;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class CircBufferSimulation {
public static void main(String[] args) {
final int bufferCapacity = 15; // max. number of items the buffer can store
final int producerCount = 1; // Number of producer threads
final int consumerCount = 1; // Number of consumer threads
final int maxProduceTime = 500; // max. production time for one item
final int maxConsumeTime = 500; // max. consumption time for one item
try {
Buffer<String> buffer = new CircularBuffer<>(String.class, bufferCapacity);
// start consumers
Consumer[] consumers = new Consumer[consumerCount];
for (int i = 0; i < consumerCount; i++) {
consumers[i] = new Consumer("Consumer_" + i, buffer, maxConsumeTime);
consumers[i].start();
}
// start producers
Producer[] producers = new Producer[producerCount];
for (int i = 0; i < producerCount; i++) {
producers[i] = new Producer("Producer_" + i, buffer, maxProduceTime);
producers[i].start();
}
// print live buffer status
ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(2);
// print occupied slots of buffer every second
scheduled.scheduleAtFixedRate(buffer::printBufferSlots, 1, 1, TimeUnit.SECONDS);
// print content of buffer every 5 seconds
scheduled.scheduleAtFixedRate(buffer::printBufferContent, 5,5, TimeUnit.SECONDS);
System.out.println("Press Enter to terminate");
System.in.read();
System.out.println("Shutting down ...");
// shutdown producers
for (Producer producer : producers) {
producer.terminate();
}
// shutdown consumers
for (Consumer consumer : consumers) {
consumer.terminate();
}
// shutdown statistics
scheduled.shutdown();
System.out.println("Simulation ended.");
} catch (Exception logOrIgnore) {
System.out.println(logOrIgnore.getMessage());
}
}
private static class Producer extends Thread {
private volatile boolean running = true;
private final Buffer<String> buffer;
private final int maxProduceTime;
public Producer(String name, Buffer<String> buffer, int maxProduceTime) {
super(name);
this.buffer = buffer;
this.maxProduceTime = maxProduceTime;
}
public void terminate() {
running = false;
}
@Override
public void run() {
running = true;
int number = 1;
try {
while (running) {
buffer.put("#" + number++);
sleep((int) (100 + Math.random() * maxProduceTime));
}
} catch (InterruptedException ex) {
System.err.println("Interrupted in " + getName() + ": " + ex.getMessage());
}
}
}
private static class Consumer extends Thread {
private volatile boolean running = true;
private final Buffer<String> buffer;
private final int maxConsumeTime;
public Consumer(String name, Buffer<String> buffer, int maxConsumeTime) {
super(name);
this.buffer = buffer;
this.maxConsumeTime = maxConsumeTime;
}
public void terminate() {
running = false;
}
@Override
public void run() {
running = true;
try {
while (running) {
buffer.get();
Thread.sleep(100 + (int) (Math.random() * maxConsumeTime));
}
} catch (InterruptedException ex) {
System.err.println("Interrupted in " + getName() + ": " + ex.getMessage());
}
}
}
}

View File

@ -0,0 +1,102 @@
package ch.zhaw.prog2.circularbuffer;
import java.lang.reflect.Array;
public class CircularBuffer<T> implements Buffer<T> {
private static final char EMPTY = '-';
private static final char FILLED = '*';
private final StringBuffer printout;
private final T[] items;
private int count = 0;
private int insertPosition = 0;
private int outputPosition = 0;
@SuppressWarnings("unchecked")
public CircularBuffer(Class<T> clazz, int bufferSize) {
printout = new StringBuffer();
if (bufferSize <= 1)
bufferSize = 1;
this.items = (T[]) Array.newInstance(clazz, bufferSize);
}
public boolean put(T item) {
if (this.full())
return false;
items[insertPosition] = item;
insertPosition = (insertPosition + 1) % items.length;
count++;
return true;
}
public T get() {
if (empty())
return null;
T item = items[outputPosition];
outputPosition = (outputPosition + 1) % items.length;
count--;
return item;
}
public boolean empty() {
return count == 0;
}
public boolean full() {
return count >= items.length;
}
public int count() {
return count;
}
public void printBufferSlots() {
printout.delete(0, printout.length());
int i = 0;
// from where to where is the buffer filled?
printout.append("filled where: ||");
if (full()) {
for (i = 0; i < items.length; i++)
printout.append(FILLED);
} else {
char c1;
char c2;
if (insertPosition < outputPosition) { // if iPos < oPos, the buffer
// starts with
// filled slots at pos. 0; otherwise it
// starts with empty slots
c1 = FILLED; // * -> filled slot
c2 = EMPTY; // - -> empty slot
} else {
c1 = EMPTY;
c2 = FILLED;
}
for (i = 0; i < outputPosition && i < insertPosition; i++)
printout.append(c1);
for (; i < outputPosition || i < insertPosition; i++)
printout.append(c2);
for (; i < items.length; i++)
printout.append(c1);
}
printout.append("|| how full: || ");
// how full is the buffer generally?
for (i = 0; i < count; i++)
printout.append(FILLED);
for (; i < items.length; i++)
printout.append(EMPTY);
printout.append("||");
System.out.println(printout);
}
public void printBufferContent() {
System.out.println("Anzahl Elemente im Puffer: " + count);
for (int i = 0; i < count; i++) {
int index = (outputPosition + i) % items.length;
System.out.println("index: " + index + " wert: " + items[index]);
}
}
}

View File

@ -0,0 +1,37 @@
package ch.zhaw.prog2.circularbuffer;
public class GuardedCircularBuffer<T> implements Buffer<T> {
private CircularBuffer<T> buffer;
public GuardedCircularBuffer(Class<T> clazz, int bufferSize) {
buffer = new CircularBuffer<>(clazz, bufferSize);
}
public boolean put(T item) throws InterruptedException {
return buffer.put(item);
}
public T get() throws InterruptedException {
return buffer.get();
}
public void printBufferSlots() {
buffer.printBufferSlots();
}
public void printBufferContent() {
buffer.printBufferContent();
}
public boolean empty() {
return false;
}
public boolean full() {
return true;
}
public int count() {
return 0;
}
}

View File

@ -0,0 +1,223 @@
package ch.zhaw.prog2.circularbuffer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
class GuardedCircularBufferTest {
private static final int BUFFER_SIZE = 5;
private static final String DEFAULT_ITEM = "Item";
private GuardedCircularBuffer<String> buffer;
@BeforeEach
void setUp() {
buffer = new GuardedCircularBuffer<>(String.class, BUFFER_SIZE);
}
@AfterEach
void tearDown() {
buffer = null;
}
@Test
void testEmpty() {
assertTrue(buffer.empty(), "Must return true if buffer is empty");
assertDoesNotThrow(() -> { buffer.put("Some content"); }, "Must not throw an exception");
assertFalse(buffer.empty(), "Must return false if buffer is not empty");
}
@Test
void testFull() {
assertFalse(buffer.full(), "Must return false if buffer is empty");
for(int num=0; num < BUFFER_SIZE; num++) {
String item = DEFAULT_ITEM + " " + num;
assertDoesNotThrow(() -> { buffer.put(item); }, "Must not throw an exception");
}
assertTrue(buffer.full(), "Must return true if buffer is full");
assertDoesNotThrow(() -> { buffer.get(); }, "Must not throw an exception");
assertFalse(buffer.full(), "Must return false if buffer is not full");
}
@Test
void testCount() {
assertEquals(0, buffer.count(), "Initial should be 0");
for(int num=1; num <= BUFFER_SIZE; num++) {
String item = DEFAULT_ITEM + " " + num;
assertDoesNotThrow(() -> { buffer.put(item); }, "Must not throw an exception");
assertEquals(num, buffer.count());
}
}
@Test
void testSinglePutGet() {
assertTrue(buffer.empty(), "Make sure buffer is empty");
assertDoesNotThrow(() -> { buffer.put(DEFAULT_ITEM); }, "Must not throw an exception");
assertEquals(1, buffer.count());
AtomicReference<String> returnItem = new AtomicReference<>();
assertDoesNotThrow(() -> { returnItem.set(buffer.get()); }, "Must not throw an exception");
assertEquals(DEFAULT_ITEM, returnItem.get());
}
@Test
void testMultiplePutGet() {
assertTrue(buffer.empty(), "Make sure buffer is empty");
// write items
for (int num = 0; num < BUFFER_SIZE; num++) {
String item = DEFAULT_ITEM + " " + num;
assertDoesNotThrow(() -> {
buffer.put(item);
}, "Must not throw an exception");
}
// read items in same order
for (int num = 0; num < BUFFER_SIZE; num++) {
AtomicReference<String> returnItem = new AtomicReference<>();
assertDoesNotThrow(() -> { returnItem.set(buffer.get()); }, "Must not throw an exception");
assertEquals(DEFAULT_ITEM + " " + num, returnItem.get());
}
}
@Test
void testBlockProduce() {
assertTrue(buffer.empty(), "Make sure buffer is empty");
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
executorService.execute(new Producer<String>(buffer, BUFFER_SIZE+1, DEFAULT_ITEM));
TimeUnit.MILLISECONDS.sleep(100);
assertTrue(buffer.full(), "Buffer should be full");
executorService.shutdown();
assertFalse(executorService.awaitTermination(3, TimeUnit.SECONDS), "Executor should be blocking");
} catch (InterruptedException e) {
fail("Interrupted executor", e);
} finally {
executorService.shutdownNow();
}
}
@Test
void testBlockConsume() {
assertTrue(buffer.empty(), "Make sure buffer is empty");
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
executorService.execute(new Consumer<String>(buffer, 2));
TimeUnit.MILLISECONDS.sleep(100);
assertTrue(buffer.empty(), "Buffer should be empty");
executorService.shutdown();
assertFalse(executorService.awaitTermination(3, TimeUnit.SECONDS), "Executor should be blocking");
} catch (InterruptedException e) {
fail("Interrupted executor", e);
} finally {
executorService.shutdownNow();
}
}
@Test
void testProduceThenConsume() {
assertTrue(buffer.empty(), "Make sure buffer is empty");
try {
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new Producer<String>(buffer, BUFFER_SIZE, DEFAULT_ITEM));
TimeUnit.MILLISECONDS.sleep(100);
assertEquals(BUFFER_SIZE, buffer.count(), "Buffer must contain " + BUFFER_SIZE + " items");
Consumer<String> consumer = new Consumer<>(buffer, BUFFER_SIZE);
executorService.execute(consumer);
TimeUnit.MILLISECONDS.sleep(100);
assertEquals(0, buffer.count(), "Buffer must contain 0 items");
Object[] expected = Stream.generate(() -> DEFAULT_ITEM).limit(BUFFER_SIZE).toArray();
assertArrayEquals(expected, consumer.getItems().toArray());
executorService.shutdown();
assertTrue(executorService.awaitTermination(3, TimeUnit.SECONDS), "Timeout shutting down Executor");
} catch (InterruptedException e) {
fail("Interrupted executor", e);
}
}
@Test
void testProduceAndConsume() {
assertTrue(buffer.empty(), "Make sure buffer is empty");
ExecutorService executorService = Executors.newFixedThreadPool(2);
try {
executorService.execute(new Producer<String>(buffer, 10*BUFFER_SIZE, "Item"));
TimeUnit.SECONDS.sleep(1);
Consumer<String> consumer = new Consumer<>(buffer, 10*BUFFER_SIZE);
executorService.execute(consumer);
TimeUnit.SECONDS.sleep(1);
List<String> receivedItems = consumer.getItems();
assertEquals(10*BUFFER_SIZE, receivedItems.size());
Object[] expected = Stream.generate(() -> DEFAULT_ITEM).limit(10*BUFFER_SIZE).toArray();
assertArrayEquals(expected, consumer.getItems().toArray());
executorService.shutdown();
assertTrue(executorService.awaitTermination(3, TimeUnit.SECONDS), "Timeout shutting down Executor");
} catch (InterruptedException e) {
fail("Interrupted executor", e);
} finally {
executorService.shutdownNow();
}
}
private class Producer<T> implements Runnable {
private GuardedCircularBuffer<T> buffer;
private int numItems;
private T item;
public Producer(GuardedCircularBuffer<T> buffer, int numItems, T item) {
this.buffer = buffer;
this.numItems = numItems;
this.item = item;
}
@Override
public void run() {
for (int num = 0; num < numItems; num++) {
try {
buffer.put(item);
} catch (InterruptedException e) {
System.err.println("Interrupted Producer at " + num + ": " + e.getMessage());
}
}
}
}
private class Consumer<T> implements Runnable {
private GuardedCircularBuffer<T> buffer;
private int numItems;
private List<T> items;
public Consumer(GuardedCircularBuffer<T> buffer, int numItems) {
this.buffer = buffer;
this.numItems = numItems;
this.items = new ArrayList<>();
}
public List<T> getItems() {
return items;
}
@Override
public void run() {
for (int num = 0; num < numItems; num++) {
try {
this.items.add(buffer.get());
} catch (InterruptedException e) {
System.err.println("Interrupted Consumer at " + num + ": " + e.getMessage());
}
}
}
}
}

View File

@ -0,0 +1,56 @@
/*
* Gradle build configuration for specific lab module / exercise
*/
// enabled plugins
plugins {
id 'java'
// Apply the application plugin to add support for building a CLI application.
id 'application'
}
// Project/Module information
description = 'Lab04 Philosopher'
group = 'ch.zhaw.prog2'
version = '2022.1'
// Dependency configuration
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.+'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.+'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.+'
testImplementation 'org.mockito:mockito-core:4.3.1'
}
// Test task configuration
test {
// Use JUnit platform for unit tests
useJUnitPlatform()
testLogging {
events "PASSED", "SKIPPED", "FAILED"
}
}
// Configuration for Application plugin
application {
// Define the main class for the application.
mainClass = 'ch.zhaw.prog2.philosopher.PhilosopherGui'
}
// 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
}
}

View File

@ -0,0 +1,221 @@
package ch.zhaw.prog2.philosopher;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import static ch.zhaw.prog2.philosopher.ForkManager.ForkState.*;
/**
* ForkManager manages the resources (=forks), used by the philosophers
*/
class ForkManager {
// maximum concurrent threads acquiring forks - used for internal statistics
private static final LockFreeMax concurrentAcquires = new LockFreeMax();
private final Lock mutex; // shared mutex for all forks
private final int delayTime; // delay in milliseconds between acquiring / releasing the forks
private final int numForks; // amount of forks to be managed
private final Fork[] forks; // array of managed forks
/**
* Constructor to initialize the fork manager.
* @param numForks amount of forks to manage
* @param delayTime delay in milliseconds between acquiring / releasing the forks
*/
public ForkManager(int numForks, int delayTime) {
this.mutex = new ReentrantLock();
this.delayTime = delayTime;
this.numForks = numForks;
this.forks = new Fork[numForks];
for (int forkId = 0; forkId < numForks; forkId++)
forks[forkId] = new Fork(forkId, mutex);
}
/**
* Acquire both forks of a specific philosopher.
* Here you implement your synchronization strategy.
* There should be always a delay between taking the two forks (see {@link #forkDelay()})
* @param philosopherId id of the philosopher
*/
public void acquireForks(int philosopherId) throws InterruptedException {
// acquire forks sequentially
leftFork(philosopherId).acquire(philosopherId);
forkDelay();
rightFork(philosopherId).acquire(philosopherId);
}
/**
* Release both forks for a specific philosopher.
* There should be always a delay between releasing the two forks.
*
* @param philosopherId id of the philosopher
*/
public void releaseForks(int philosopherId) throws InterruptedException {
// order of releasing does not matter
leftFork(philosopherId).release(philosopherId);
forkDelay();
rightFork(philosopherId).release(philosopherId);
}
/**
* Returns the right fork of the given philosopher
* @param philosopherId id of philosopher to get the right fork
* @return right fork of the specified philosopher
*/
private Fork rightFork(int philosopherId) {
return forks[(numForks + philosopherId - 1) % numForks];
}
/**
* Returns the left fork of the given philosopher
* @param philosopherId id of philosopher to get the left fork
* @return left fork of the specified philosopher
*/
private Fork leftFork(int philosopherId) {
return forks[philosopherId];
}
/**
* Waits {@link #delayTime} milliseconds between taking the forks.
* @throws InterruptedException if the thread is interrupted during wait
*/
void forkDelay() throws InterruptedException {
try {
Thread.sleep(this.delayTime);
} catch (InterruptedException e) {
throw new InterruptedException("Interrupted fork delay - " + e.getMessage());
}
}
/**
* Get the maximum number of threads concurrently acquired forks - used to measure parallelism.
* @return maximum number of concurrent threads acquiring forks
*/
public int getConcurrentAcquires() {
return concurrentAcquires.maxValue.intValue();
}
/**
* Test if all forks are in a specific state. Used to detect deadlocks.
* @param state State of fork to test for
* @return true if all forks are in the given state, false otherwise
*/
public boolean areAllForksInState(ForkState state) {
return Arrays.stream(forks).allMatch(fork -> fork.state == state);
}
@Override
public String toString() {
return "forks = " + Arrays.toString(forks);
}
/**
* Possible states of a fork.
* The WAITING state is used as a temporary state to mark an OCCUPIED fork,
* if another philosopher is waiting for it (see {@link Fork#acquire(int)}.
*/
enum ForkState {
FREE, WAITING, OCCUPIED
}
/**
* Class holding the state of a single fork.
* It also provides a condition (waiting room) to allow philosophers to wait for this fork.
*/
private static class Fork {
private final int id; // unique id of fork
private final Lock mutex; // shared mutex of fork-manager
private final Condition cond; // specific wait condition for this fork
private ForkState state = FREE; // current state of the fork
private int ownerId; // temporary owning philosopher of the fork
/**
* Constructor for a fork.
* @param id unique id of the fork
* @param mutex shared mutex to use for wait conditions
*/
public Fork(int id, Lock mutex) {
this.id = id;
this.ownerId = -1;
this.mutex = mutex;
this.cond = mutex.newCondition();
}
/**
* Acquire the fork for a specific philosopher (applicantId).
* The applicantId is used to remember the current "owner" of a fork.
* @param applicantId id of the philosopher to acquire the fork
* @throws InterruptedException if the thread is interrupted during waiting for the fork
*/
public void acquire(int applicantId) throws InterruptedException {
try {
mutex.lock();
while (state != FREE) {
state = WAITING;
cond.await();
}
concurrentAcquires.increment();
TimeUnit.MILLISECONDS.sleep(10);
state = OCCUPIED;
ownerId = applicantId;
} catch (InterruptedException e) {
throw new InterruptedException("Interrupted acquire fork " + id + " - " + e.getMessage());
} finally {
concurrentAcquires.decrement();
mutex.unlock();
}
}
/**
* Release the fork for a specific philosopher (applicantId).
* The applicantId is used to verify that a fork is released by the "owner".
* @param applicantId id of the philosopher releasing the fork
* @throws InterruptedException if the thread is interrupted during releasing the fork
*/
public void release(int applicantId) throws InterruptedException {
try {
mutex.lock();
if (ownerId != applicantId) throw new IllegalStateException("Release fork " + id + " not owned by " + applicantId);
TimeUnit.MILLISECONDS.sleep(10);
state = FREE;
ownerId = -1;
cond.signal();
} catch (InterruptedException e) {
throw new InterruptedException("Interrupted release fork " + id + " - " + e.getMessage());
} finally {
mutex.unlock();
}
}
@Override
public String toString() {
return "Fork {" + "id=" + id + ", state=" + state + ", owner=" + ownerId + '}';
}
}
/*
* Determine maximum value without using locks
*
* This class is used here to detect the maximum numbers of threads that can be in the same codeblock.
* To get a good measurement:
* - Use a sleep() between increment() and decrement()
* - Use increment() after acquiring the lock and decrement() before releasing the lock
* - Do not call any method that does release the lock: Do not use await() between increment() and decrement()
*/
private static class LockFreeMax {
private final AtomicInteger value = new AtomicInteger(0);
private final AtomicInteger maxValue = new AtomicInteger(0);
public void increment() {
maxValue.accumulateAndGet(value.incrementAndGet(), Math::max);
}
public void decrement() {
value.decrementAndGet();
}
}
}

View File

@ -0,0 +1,163 @@
package ch.zhaw.prog2.philosopher;
import ch.zhaw.prog2.philosopher.PhilosopherTable.Philosopher;
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Observable;
import java.util.Observer;
import static ch.zhaw.prog2.philosopher.PhilosopherTable.PhilosopherState.*;
/**
* Main- and Graphical-UI classes for the philosopher exercise.
* You should not change these classes, just use it to run the application.
*/
public class PhilosopherGui extends JFrame {
private static final int DEFAULT_PHILOSOPHER_COUNT = 5;
private static final int DEFAULT_BASE_TIME = 75; // milliseconds
private final PhilosopherTable table;
public static void main(String[] args) {
int philosopherCount = args.length >=1 ? Integer.parseInt(args[0]) : DEFAULT_PHILOSOPHER_COUNT;
int baseTime = args.length >= 2 ? Integer.parseInt(args[1]) : DEFAULT_BASE_TIME;
new PhilosopherGui(philosopherCount, baseTime);
}
public PhilosopherGui(int philosopherCount, int baseTime) {
setTitle("Philosopher");
setVisible(true);
setVisible(false);
Insets insets = getInsets();
setSize(insets.left + insets.right + 400, insets.top + insets.bottom + 400);
table = new PhilosopherTable(philosopherCount, baseTime);
PhilosopherPanel panel = new PhilosopherPanel(table, philosopherCount);
new ConsoleLogger(table);
table.start();
setContentPane(panel);
setVisible(true);
repaint();
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
closing();
}
});
}
private void closing() {
table.stop();
System.exit(0);
}
static class PhilosopherPanel extends JPanel implements Observer {
private static final long serialVersionUID = 5113281871592746242L;
private final PhilosopherTable table;
private final int philosopherCount; // greater than 2
public PhilosopherPanel(PhilosopherTable table, int philosopherCount) {
this.philosopherCount = philosopherCount;
this.table = table;
table.addObserver(this);
//new Timer(100, e -> repaint()).start(); // autorepaint periodically
}
public void paint(Graphics g) {
Insets insets = getInsets();
Dimension dim = getSize();
int length = Math.min(dim.width, dim.height);
double teta;
double tetaIn;
double phi = 2 * Math.PI / philosopherCount;
int plateRadius = (int) (Math.sqrt(Math.pow(length / 2, 2.0)
- Math.pow(Math.cos(phi) * (length / 2), 2.0) + Math.sin(phi)
* (length / 2)) * 0.25);
int tableRadius = (int) (length / 2 - plateRadius) - 10;
int halfStickLength = (int) (plateRadius * 1.25);
int centerX = length / 2 + insets.left;
int centerY = length / 2 + insets.top;
super.paint(g);
for (int philosopherId = 0; philosopherId < philosopherCount; philosopherId++) {
int transCenterX = centerX - plateRadius;
int transCenterY = centerY - plateRadius;
teta = 0;
switch (table.getPhilosopher(philosopherId).getState()) {
case THINKING:
g.setColor(Color.blue);
break;
case HUNGRY:
g.setColor(Color.red);
break;
case EATING:
g.setColor(Color.yellow);
break;
}
int xPositionPlate = (int) Math.round(transCenterX + tableRadius * Math.cos(philosopherId * phi));
int yPositionPlate = (int) Math.round(transCenterY + tableRadius * Math.sin(philosopherId * phi));
g.fillOval(xPositionPlate, yPositionPlate, 2 * plateRadius, 2 * plateRadius);
g.setColor(Color.black);
g.setFont(new Font(Font.DIALOG, Font.BOLD, 20));
g.drawString(""+philosopherId, xPositionPlate+plateRadius-5, yPositionPlate+plateRadius+10 );
if (table.getPhilosopher(philosopherId).getState() == EATING) {
teta = (-phi / 7);
}
if (table.getPhilosopher(table.leftNeighbourId(philosopherId)).getState() == EATING) {
teta = phi / 7;
}
tetaIn = teta * 1.75;
int xStickInner = (int) Math.round(centerX + (tableRadius - halfStickLength) * Math.cos(philosopherId * phi + phi / 2 + tetaIn));
int yStickInner = (int) Math.round(centerY + (tableRadius - halfStickLength) * Math.sin(philosopherId * phi + phi / 2 + tetaIn));
int xStickOuter = (int) Math.round(centerX + (tableRadius + halfStickLength) * Math.cos(philosopherId * phi + phi / 2 + teta));
int yStickOuter = (int) Math.round(centerY + (tableRadius + halfStickLength) * Math.sin(philosopherId * phi + phi / 2 + teta));
g.drawLine(xStickInner, yStickInner, xStickOuter, yStickOuter);
g.drawString(""+philosopherId, xStickInner, yStickInner);
}
}
public void update(Observable o, Object arg) {
repaint();
}
}
static class ConsoleLogger implements Observer {
public ConsoleLogger(PhilosopherTable table) {
table.addObserver(this);
}
public void update(Observable o, Object arg) {
Philosopher philosopher = arg != null ? (Philosopher) arg : null;
if (philosopher == null) {
System.out.println("Application starting");
return;
}
System.out.println("Philosopher " + philosopher.getId() + " " + getStateString(philosopher));
}
private String getStateString(Philosopher philosopher) {
return switch (philosopher.getState()) {
case EATING -> "starts eating";
case THINKING -> "starts thinking";
case HUNGRY -> "is getting hungry";
};
}
}
}

View File

@ -0,0 +1,257 @@
package ch.zhaw.prog2.philosopher;
import java.util.Arrays;
import java.util.Observable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static ch.zhaw.prog2.philosopher.ForkManager.ForkState.*;
import static ch.zhaw.prog2.philosopher.PhilosopherTable.PhilosopherState.*;
/**
* PhilosopherTable represent the model.
* It is responsible to create, manage and terminate the {@link Philosopher} threads.
*/
class PhilosopherTable extends Observable {
private final int baseTime;
private final int philosopherCount;
private final Philosopher[] philosophers;
private final ForkManager forkManager;
private volatile boolean running;
private boolean isDeadlock = false;
private ExecutorService philosopherExecutor;
private ScheduledExecutorService watchdogExecutor;
public PhilosopherTable(int philosopherCount, int baseTime) {
this.baseTime = baseTime;
this.philosopherCount = philosopherCount;
this.philosophers = new Philosopher[philosopherCount];
this.forkManager = new ForkManager(philosopherCount, baseTime);
for (int philosopherId = philosopherCount - 1; philosopherId >= 0; philosopherId--) {
philosophers[philosopherId] = new Philosopher(philosopherId);
}
notifyStateChange(null);
System.out.printf("Creating table (%d Philosophers, base time = %dms )%n ...", philosopherCount, baseTime);
}
/**
* Get deadlock state
* @return true if deadlock is detected
*/
boolean isDeadlock() {
return isDeadlock;
}
/**
* Get running state
* @return true if table is in running state
*/
boolean isRunning() {
return running;
}
/**
* Internal method to notify GUI-Observers that the state of a philosopher has changed.
* @param sender philosopher whose state has changed
*/
private synchronized void notifyStateChange(Philosopher sender) {
setChanged();
notifyObservers(sender);
checkNeighbourState(sender);
}
/**
* Internal method to verify that no two neighbouring philosophers can be in state EATING
* @param philosopher philosopher to check neighbour states for
*/
private void checkNeighbourState(Philosopher philosopher) {
if (philosopher != null && philosopher.state == EATING) {
int id = philosopher.id;
PhilosopherState leftState = philosophers[leftNeighbourId(id)].state;
PhilosopherState rightState = philosophers[rightNeighbourId(id)].state;
int eatingNeighbour = leftState == EATING ? leftNeighbourId(id) :
rightState == EATING ? rightNeighbourId(id) :
-1;
if (eatingNeighbour >= 0) {
System.out.println("ILLEGAL STATE: Two neighbouring Philosophers are eating: " + id + " | " + eatingNeighbour);
stop();
}
}
}
/**
* Internal method to check if there is a deadlock
*/
private void checkDeadlock() {
this.isDeadlock = forkManager.areAllForksInState(WAITING) && areAllPhilosophersInState(HUNGRY);
if (isDeadlock) {
System.out.println("DEADLOCK: All Philosophers are starving!!!");
stop();
}
}
/**
* Test if all philosophers are in a specific state. Used to detect deadlocks.
* @param state State of philosopher to test for
* @return true if all philosophers are in the given state, false otherwise
*/
private boolean areAllPhilosophersInState(PhilosopherState state) {
return Arrays.stream(philosophers).allMatch(philosopher -> philosopher.state == state );
}
/**
* Start the philosopher processes
*/
public void start() {
this.running = true;
System.out.println("Start deadlock watchdog ...");
watchdogExecutor = Executors.newSingleThreadScheduledExecutor();
watchdogExecutor.scheduleAtFixedRate(this::checkDeadlock, 2, 2, TimeUnit.SECONDS);
System.out.println("Starting philosophers ...");
philosopherExecutor = Executors.newFixedThreadPool(philosopherCount);
for (Philosopher philosopher : philosophers) {
philosopherExecutor.execute(philosopher);
}
}
/**
* Stop the philosopher processes
*/
public void stop() {
if (running) {
this.running = false;
System.out.println("Stopping deadlock watchdog ...");
watchdogExecutor.shutdown();
System.out.println("Stopping philosophers ...");
philosopherExecutor.shutdownNow();
System.out.println("Final state: \n" + this);
System.out.format("Detected at most %d concurrent Philosophers acquiring forks%n", forkManager.getConcurrentAcquires());
} else {
System.err.println("Stop called while not running.");
}
}
/**
* Get a specific philosopher by id
* @param philosopherId id of philosopher to return
* @return philosopher object for specified id
*/
public Philosopher getPhilosopher(int philosopherId) {
return philosophers[philosopherId];
}
/**
* Return the id of the philosopher sitting to the right of the given philosopher id
* @param philosopherId id of the philosopher
* @return id of the neighbour to the right
*/
public int rightNeighbourId(int philosopherId) {
return (philosopherCount + philosopherId - 1) % philosopherCount;
}
/**
* Return the id of the philosopher sitting to the left of the given philosopher id
* @param philosopherId id of the philosopher
* @return id of the neighbour to the left
*/
public int leftNeighbourId(int philosopherId) {
return (philosopherId + 1) % philosopherCount;
}
@Override
public String toString() {
return "PhilosopherTable { running = " + running +
"\n philosophers = " + Arrays.toString(philosophers) +
"\n " + forkManager +
"\n}";
}
/**
* Possible states of a philosopher
*/
enum PhilosopherState {
THINKING, HUNGRY, EATING
}
/**
* Implementation of the Philosopher as an inner class.
* This class should not be changed!
* All logic for acquiring and releasing forks must be implemented in the {@link ForkManager} class
* The {@link PhilosopherTable#running} variable from the outer class {@link PhilosopherTable} is used to control
* termination of the run loop.
* Also, to notify the observers (GUI) the {@link PhilosopherTable#notifyStateChange(Philosopher)} method of
* the outer class is used.
*/
class Philosopher implements Runnable {
private static final int THINK_TIME_FACTOR = 5;
private static final int EAT_TIME_FACTOR = 1;
private final int id;
private PhilosopherState state = THINKING;
public Philosopher(int id) {
this.id = id;
}
public PhilosopherState getState() {
return state;
}
public long getId() {
return id;
}
private void think() throws InterruptedException {
try {
state = THINKING;
notifyStateChange(this);
Thread.sleep((int) (Math.random() * THINK_TIME_FACTOR * baseTime));
} catch (InterruptedException e) {
throw new InterruptedException("Interrupted thinking - " + e.getMessage());
}
}
private void eat() throws InterruptedException {
try {
state = EATING;
notifyStateChange(this);
Thread.sleep((int) (Math.random() * EAT_TIME_FACTOR * baseTime));
} catch (InterruptedException e) {
throw new InterruptedException("Interrupted eating - " + e.getMessage());
}
}
private void takeForks() throws InterruptedException {
state = HUNGRY;
notifyStateChange(this);
forkManager.acquireForks(id);
}
private void putForks() throws InterruptedException {
state = THINKING; // needed to prevent false positive in checkNeighborState
forkManager.releaseForks(id);
}
@Override
public void run() {
System.out.println("Starting Philosopher " + id);
try {
while (running) {
think();
takeForks();
eat();
putForks();
}
} catch (InterruptedException e) {
System.err.println("Interrupted " + this + " : " + e.getMessage());
}
System.out.println("Stopping Philosopher " + id);
}
@Override
public String toString() {
return "Philosopher {" + "id=" + id + ", state=" + state + "}";
}
}
}

View File

@ -0,0 +1,85 @@
package ch.zhaw.prog2.philosopher;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.*;
class PhilosopherTest {
private static final int PHILOSOPHER_COUNT = 5;
private static final int BASE_TIME = 75; // milliseconds
private PhilosopherTable table;
@BeforeEach
void setUp() {
}
@AfterEach
void tearDown() {
}
@Test
void testPhilosopherTable() {
table = new PhilosopherTable(PHILOSOPHER_COUNT, BASE_TIME);
TableStateObserver tableStateObserver = new TableStateObserver(table);
try {
table.start();
int timePassed = 0;
while (!table.isDeadlock() && timePassed < 55) {
TimeUnit.SECONDS.sleep(5);
timePassed += 5;
}
} catch (InterruptedException e) {
fail("Table interrupted", e);
} finally {
table.deleteObserver(tableStateObserver);
assertFalse(table.isDeadlock(),"Deadlock detected: " + table);
table.stop();
}
}
static class TableStateObserver implements Observer {
final static boolean VERBOSE = false;
final PhilosopherTable table;
public TableStateObserver(PhilosopherTable table) {
this.table = table;
table.addObserver(this);
}
public void update(Observable o, Object arg) {
PhilosopherTable.Philosopher philosopher = arg != null ? (PhilosopherTable.Philosopher) arg : null;
if (VERBOSE) printState(philosopher);
if (table.isDeadlock()) {
fail("Deadlock detected: " + table);
} else if (!table.isRunning()) {
fail("Table stopped for other reason: " + table);
}
}
private void printState(PhilosopherTable.Philosopher philosopher) {
if (philosopher == null) {
System.out.println("Application starting");
return;
}
System.out.println("Philosopher " + philosopher.getId() + " " + getStateString(philosopher));
}
private String getStateString(PhilosopherTable.Philosopher philosopher) {
return switch (philosopher.getState()) {
case EATING -> "starts eating";
case THINKING -> "starts thinking";
case HUNGRY -> "is getting hungry";
};
}
}
}

View File

@ -0,0 +1,22 @@
/*
* Gradle build configuration for specific lab module / exercise
* Default declarations can be found in the lab main build configuration (../../gradle.build)
* Declarations in this file extend or override the default values.
*/
// the Java plugin is added by default in the main lab configuration
plugins {
// Apply the application plugin to add support for building a CLI application.
id 'application'
}
description = 'Lab04 TrafficLight'
dependencies {
}
// Configuration for Application plugin
application {
// Define the main class for the application.
mainClass = 'ch.zhaw.prog2.trafficlight.TrafficLightOperation'
}

View File

@ -0,0 +1,28 @@
package ch.zhaw.prog2.trafficlight;
class Car extends Thread {
private final TrafficLight[] trafficLights;
private int pos;
public Car(String name, TrafficLight[] trafficLights) {
super(name);
this.trafficLights = trafficLights;
pos = 0; // start at first light
start();
}
public synchronized int position() {
return pos;
}
private void gotoNextLight() {
// ToDo: Helper method to move car to next light
}
@Override
public void run() {
while (true) {
// ToDo: drive endlessly through all lights
}
}
}

View File

@ -0,0 +1,22 @@
package ch.zhaw.prog2.trafficlight;
class TrafficLight {
private boolean red;
public TrafficLight() {
red = true;
}
public synchronized void passby() {
// ToDo: wait as long the light is red
}
public synchronized void switchToRed() {
// ToDo: set light to red
}
public synchronized void switchToGreen() {
// Todo: set light to green
// waiting cars can now pass by
}
}

View File

@ -0,0 +1,67 @@
package ch.zhaw.prog2.trafficlight;
public class TrafficLightOperation {
private static volatile boolean running = true;
public static void terminate () {
running = false;
}
public static void main(String[] args) {
TrafficLight[] trafficLights = new TrafficLight[7];
Car[] cars = new Car[20];
for (int i = 0; i < trafficLights.length; i++)
trafficLights[i] = new TrafficLight();
for (int i = 0; i < cars.length; i++) {
cars[i] = new Car("Car " + i, trafficLights);
}
// Simulation
while (running) {
for (int greenIndex = 0; greenIndex < trafficLights.length; greenIndex = greenIndex + 2) {
// Display state of simulation
System.out.println("=====================================================");
for (int j = 0; j < trafficLights.length; j++) {
String lightState;
if (j == greenIndex || j == greenIndex + 1)
lightState = "";
else
lightState = "🛑";
System.out.print(lightState + " at Light " + j + ":");
for (int carNumber = 0; carNumber < cars.length; carNumber++) {
if (cars[carNumber].position() == j)
System.out.print(" " + carNumber);
}
System.out.println();
}
System.out.println("=====================================================");
try {
Thread.sleep(3000);
} catch (InterruptedException logOrIgnore) {
System.out.println(logOrIgnore.getMessage());
}
trafficLights[greenIndex].switchToGreen();
if (greenIndex + 1 < trafficLights.length) {
trafficLights[greenIndex + 1].switchToGreen();
}
// green period
try {
Thread.sleep((int) (Math.random() * 500));
} catch (InterruptedException logOrIgnore) {
System.out.println(logOrIgnore.getMessage());
}
trafficLights[greenIndex].switchToRed();
if (greenIndex + 1 < trafficLights.length)
trafficLights[greenIndex + 1].switchToRed();
// red period
try {
Thread.sleep(1000);
} catch (InterruptedException logOrIgnore) {
System.out.println(logOrIgnore.getMessage());
}
}
}
}
}

7
gradle.properties Normal file
View File

@ -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

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -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

234
gradlew vendored Executable file
View File

@ -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" "$@"

89
gradlew.bat vendored Normal file
View File

@ -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

BIN
images/CircularBuffer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1006 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

BIN
images/PROG2-300x300.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

BIN
images/bridge_overview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
images/philosopher-ui.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

45
settings.gradle Normal file
View File

@ -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('.').name
// 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')

View File

@ -0,0 +1,22 @@
/*
* Gradle build configuration for specific lab module / exercise
* Default declarations can be found in the lab main build configuration (../../gradle.build)
* Declarations in this file extend or override the default values.
*/
// the Java plugin is added by default in the main lab configuration
plugins {
// Apply the application plugin to add support for building a CLI application.
id 'application'
}
description = 'Lab04 AccountTransfer Solution'
dependencies {
}
// Configuration for Application plugin
application {
// Define the main class for the application.
mainClass = 'ch.zhaw.prog2.account.AccountTransferSimulation'
}

View File

@ -0,0 +1,23 @@
package ch.zhaw.prog2.account;
public class Account {
private final int id;
private int balance = 0;
public Account(int id, int initialAmount) {
this.id = id;
this.balance = initialAmount;
}
public int getId() {
return id;
}
public synchronized int getBalance() {
return balance;
}
public synchronized void transferAmount(int amount) {
this.balance += amount;
}
}

View File

@ -0,0 +1,67 @@
package ch.zhaw.prog2.account;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class AccountTransferSimulation {
private static final int ITERATIONS = 10000;
public static void main(String[] args) throws InterruptedException {
Account account1 = new Account(1, 10);
Account account2 = new Account(2, 10);
Account account3 = new Account(3, 999999);
System.out.println("Start Balance:");
System.out.println("- Account1 = " + account1.getBalance());
System.out.println("- Account2 = " + account2.getBalance());
System.out.println("- Account3 = " + account3.getBalance());
System.out.println("Summed up balances of all accounts: " +
(account1.getBalance() + account2.getBalance() + account3.getBalance()));
System.out.println("Start of Transaction");
// AccountTransferTask task1 = new AccountTransferTask(account3, account1, 2);
AccountTransferTask task1 = new AccountTransferTask(account1, account3, 2);
AccountTransferTask task2 = new AccountTransferTask(account3, account2, 1);
AccountTransferTask task3 = new AccountTransferTask(account2, account1, 2);
// create ExecutorService
ExecutorService executor = Executors.newFixedThreadPool(8);
// execute transactions on accounts
System.out.println("Submitting...");
for (int count = 0; count < ITERATIONS; count++) {
executor.execute(task1);
executor.execute(task2);
executor.execute(task3);
}
System.out.println("Working...");
executor.shutdown();
// wait for completion
boolean completed = executor.awaitTermination(20, TimeUnit.SECONDS);
if (completed) {
System.out.println("Transactions completed!");
} else {
System.out.println("Transactions timed out!");
executor.shutdownNow();
}
// Print end statistics
System.out.println("Amount transferred (when enough on fromAccount):");
System.out.println("- Task 1 = " + task1.getTotalTransfer());
System.out.println("- Task 2 = " + task2.getTotalTransfer());
System.out.println("- Task 3 = " + task3.getTotalTransfer());
System.out.println("End Balance:");
System.out.println("- Account1 = " + account1.getBalance());
System.out.println("- Account2 = " + account2.getBalance());
System.out.println("- Account3 = " + account3.getBalance());
System.out.println("Summed up balances of all accounts (should be the same as at start): " +
(account1.getBalance() + account2.getBalance() + account3.getBalance()));
}
}

View File

@ -0,0 +1,58 @@
package ch.zhaw.prog2.account;
class AccountTransferTask implements Runnable {
private final Account fromAccount;
private final Account toAccount;
private final int amount;
private int totalTransfer = 0;
public AccountTransferTask(Account fromAccount, Account toAccount, int amount) {
this.fromAccount = fromAccount;
this.toAccount = toAccount;
this.amount = amount;
}
public int getTotalTransfer() {
return this.totalTransfer;
}
@Override
public void run() {
//transfer();
transferDLfree();
}
/* b) ensure no lost updates are happening */
public void transfer() {
synchronized (fromAccount) {
synchronized (toAccount) {
// Account must not be overdrawn
if (fromAccount.getBalance() >= amount) {
fromAccount.transferAmount(-amount);
toAccount.transferAmount(amount);
totalTransfer += amount;
}
}
}
}
/* c) deadlock free using ascending order of IDs */
public void transferDLfree() {
// This implementation is deadlock free using the second possibility
// -> locking of accounts in ascending order of accountID
boolean isLower = fromAccount.getId() < toAccount.getId();
Account lowerAccount = isLower ? fromAccount : toAccount;
Account higherAccount = !isLower ? fromAccount : toAccount;
synchronized (lowerAccount) {
synchronized (higherAccount) {
// Account must not be overdrawn
if (fromAccount.getBalance() >= amount) {
fromAccount.transferAmount(-amount);
toAccount.transferAmount(amount);
totalTransfer += amount;
}
}
}
}
}

View File

@ -0,0 +1,22 @@
/*
* Gradle build configuration for specific lab module / exercise
* Default declarations can be found in the lab main build configuration (../../gradle.build)
* Declarations in this file extend or override the default values.
*/
// the Java plugin is added by default in the main lab configuration
plugins {
// Apply the application plugin to add support for building a CLI application.
id 'application'
}
description = 'Lab04 Bridge Solution'
dependencies {
}
// Configuration for Application plugin
application {
// Define the main class for the application.
mainClass = 'ch.zhaw.prog2.bridge.Main'
}

View File

@ -0,0 +1,82 @@
package ch.zhaw.prog2.bridge;
import java.awt.*;
public class Car implements Runnable {
public static final int REDCAR = 0;
public static final int BLUECAR = 1;
private final int bridgeY = 95;
private final int bridgeXLeft = 210;
private final int bridgeXLeft2 = 290;
private final int bridgeXMid = 410;
private final int bridgeXRight2 = 530;
private final int bridgeXRight = 610;
private final int totalWidth = 900;
private final int initX[] = {-80, totalWidth};
private final int initY[] = {135, 55};
private final int outLeft = -200;
private final int outRight = totalWidth + 200;
int cartype;
int xpos, ypos;
Car inFront;
Image image;
TrafficController controller;
public Car(int cartype, Car inFront, Image image, TrafficController controller) {
this.cartype = cartype;
this.inFront = inFront;
this.image = image;
this.controller = controller;
if (cartype == REDCAR) {
xpos = inFront == null ? outRight : Math.min(initX[cartype], inFront.getX()-90);
} else {
xpos = inFront == null ? outLeft : Math.max(initX[cartype], inFront.getX()+90);
}
ypos = initY[cartype];
}
public void move() {
int xposOld = xpos;
if (cartype==REDCAR) {
if (inFront.getX() - xpos > 100) {
xpos += 4;
if (xpos >= bridgeXLeft && xposOld < bridgeXLeft) controller.enterLeft();
else if (xpos > bridgeXLeft && xpos < bridgeXMid) { if (ypos > bridgeY) ypos -= 2; }
else if (xpos >= bridgeXRight2 && xpos < bridgeXRight) { if (ypos < initY[REDCAR]) ypos += 2; }
else if (xpos >= bridgeXRight && xposOld < bridgeXRight) controller.leaveRight();
}
} else {
if (xpos-inFront.getX() > 100) {
xpos -= 4;
if (xpos <= bridgeXRight && xposOld > bridgeXRight) controller.enterRight();
else if (xpos < bridgeXRight && xpos > bridgeXMid) { if (ypos < bridgeY) ypos += 2; }
else if (xpos <= bridgeXLeft2 && xpos > bridgeXLeft) { if(ypos > initY[BLUECAR]) ypos -= 2; }
else if (xpos <= bridgeXLeft && xposOld > bridgeXLeft) controller.leaveLeft();
}
}
}
public void run() {
boolean outOfSight = cartype == REDCAR ? xpos > totalWidth : xpos < -80;
while (!outOfSight) {
move();
outOfSight = cartype == REDCAR ? xpos > totalWidth : xpos < -80;
try {
Thread.sleep(30);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
xpos = cartype == REDCAR ? outRight: outLeft;
}
public int getX() { return xpos; }
public void draw(Graphics g) {
g.drawImage(image, xpos, ypos, null);
}
}

View File

@ -0,0 +1,25 @@
package ch.zhaw.prog2.bridge;
import javax.swing.*;
import java.awt.*;
public class CarWindow extends JFrame {
public CarWindow(CarWorld world) {
Container c = getContentPane();
c.setLayout(new BorderLayout());
c.add("Center",world);
JButton addLeft = new JButton("Add Left");
JButton addRight = new JButton("Add Right");
addLeft.addActionListener((e) -> world.addCar(Car.REDCAR));
addRight.addActionListener((e) -> world.addCar(Car.BLUECAR));
JPanel p1 = new JPanel();
p1.setLayout(new FlowLayout());
p1.add(addLeft);
p1.add(addRight);
c.add("South",p1);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
}

View File

@ -0,0 +1,66 @@
package ch.zhaw.prog2.bridge;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
class CarWorld extends JPanel {
Image bridge;
Image redCar;
Image blueCar;
TrafficController controller;
ArrayList<Car> blueCars = new ArrayList<>();
ArrayList<Car> redCars = new ArrayList<>();
public CarWorld(TrafficController controller) {
this.controller = controller;
MediaTracker mt = new MediaTracker(this);
Toolkit toolkit = Toolkit.getDefaultToolkit();
redCar = toolkit.getImage(getClass().getResource("redcar.gif"));
mt.addImage(redCar, 0);
blueCar = toolkit.getImage(getClass().getResource("bluecar.gif"));
mt.addImage(blueCar, 1);
bridge = toolkit.getImage(getClass().getResource("bridge1.gif"));
mt.addImage(bridge, 2);
try {
mt.waitForID(0);
mt.waitForID(1);
mt.waitForID(2);
} catch (java.lang.InterruptedException e) {
System.out.println("Couldn't load one of the images");
}
redCars.add( new Car(Car.REDCAR,null,redCar,null) );
blueCars.add( new Car(Car.BLUECAR,null,blueCar,null) );
setPreferredSize( new Dimension(bridge.getWidth(null), bridge.getHeight(null)) );
}
@Override
public void paintComponent(Graphics g) {
g.drawImage(bridge,0,0,this);
for (Car c : redCars) c.draw(g);
for (Car c : blueCars) c.draw(g);
}
public void addCar(final int cartype) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Car car;
if (cartype==Car.REDCAR) {
car = new Car(cartype,redCars.get(redCars.size()-1),redCar,controller);
redCars.add(car);
} else {
car = new Car(cartype,blueCars.get(blueCars.size()-1),blueCar,controller);
blueCars.add(car);
}
new Thread(car).start();
}
});
}
}

View File

@ -0,0 +1,34 @@
package ch.zhaw.prog2.bridge;
public class Main {
private static void nap(int ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
public static void main(String[] a) {
// final TrafficController controller = new TrafficController();
// final TrafficController controller = new TrafficControllerA();
// final TrafficController controller = new TrafficControllerB();
final TrafficController controller = new TrafficControllerC();
final CarWorld world = new CarWorld(controller);
final CarWindow win = new CarWindow(world);
win.pack();
win.setVisible(true);
new Thread(new Runnable() {
public void run() {
while (true) {
nap(25);
win.repaint();
}
}
}).start();
}
}

View File

@ -0,0 +1,19 @@
package ch.zhaw.prog2.bridge;
/*
* Controls the traffic passing the bridge
*/
public class TrafficController {
/* Called when a car wants to enter the bridge form the left side */
public void enterLeft() {}
/* Called when a wants to enter the bridge form the right side */
public void enterRight() {}
/* Called when the car leaves the bridge on the left side */
public void leaveLeft() {}
/* Called when the car leaves the bridge on the right side */
public void leaveRight() {}
}

View File

@ -0,0 +1,61 @@
package ch.zhaw.prog2.bridge;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TrafficControllerA extends TrafficController {
private boolean bridgeOccupied = false;
private Lock mutex = new ReentrantLock();
private Condition enterBridge = mutex.newCondition();
public void enterLeft() {
mutex.lock();
try {
while(bridgeOccupied) {
enterBridge.await();
}
bridgeOccupied = true;
} catch (InterruptedException e) {
System.err.println("Interrupt: " + e.getMessage());
} finally {
mutex.unlock();
}
}
public void enterRight() {
mutex.lock();
try {
while(bridgeOccupied) {
enterBridge.await();
}
bridgeOccupied = true;
} catch (InterruptedException e) {
System.err.println("Interrupt: " + e.getMessage());
} finally {
mutex.unlock();
}
}
public void leaveLeft() {
mutex.lock();
try {
bridgeOccupied = false;
enterBridge.signal();
} finally {
mutex.unlock();
}
}
public void leaveRight() {
mutex.lock();
try {
bridgeOccupied = false;
enterBridge.signal();
} finally {
mutex.unlock();
}
}
}

View File

@ -0,0 +1,62 @@
package ch.zhaw.prog2.bridge;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TrafficControllerB extends TrafficController {
private boolean bridgeOccupied = false;
private Lock mutex = new ReentrantLock();
private Condition enterLeft = mutex.newCondition();
private Condition enterRight = mutex.newCondition();
public void enterLeft() {
mutex.lock();
try {
while(bridgeOccupied) {
enterLeft.await();
}
bridgeOccupied = true;
} catch (InterruptedException e) {
System.err.println("Interrupt: " + e.getMessage());
} finally {
mutex.unlock();
}
}
public void enterRight() {
mutex.lock();
try {
while(bridgeOccupied) {
enterRight.await();
}
bridgeOccupied = true;
} catch (InterruptedException e) {
System.err.println("Interrupt: " + e.getMessage());
} finally {
mutex.unlock();
}
}
public void leaveLeft() {
mutex.lock();
try {
bridgeOccupied = false;
enterLeft.signal(); // wake-up car on opposite side
} finally {
mutex.unlock();
}
}
public void leaveRight() {
mutex.lock();
try {
bridgeOccupied = false;
enterRight.signal(); // wake-up car on opposite side
} finally {
mutex.unlock();
}
}
}

View File

@ -0,0 +1,69 @@
package ch.zhaw.prog2.bridge;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class TrafficControllerC extends TrafficController {
private boolean bridgeOccupied = false;
private ReentrantLock mutex = new ReentrantLock();
private Condition enterLeft = mutex.newCondition();
private Condition enterRight = mutex.newCondition();
public void enterLeft() {
mutex.lock();
try {
while(bridgeOccupied) {
enterLeft.await();
}
bridgeOccupied = true;
} catch (InterruptedException e) {
System.err.println("Interrupt: " + e.getMessage());
} finally {
mutex.unlock();
}
}
public void enterRight() {
mutex.lock();
try {
while(bridgeOccupied) {
enterRight.await();
}
bridgeOccupied = true;
} catch (InterruptedException e) {
System.err.println("Interrupt: " + e.getMessage());
} finally {
mutex.unlock();
}
}
public void leaveLeft() {
mutex.lock();
try {
bridgeOccupied = false;
if (mutex.hasWaiters(enterRight)) {
enterRight.signal();
} else {
enterLeft.signal();
}
} finally {
mutex.unlock();
}
}
public void leaveRight() {
mutex.lock();
try {
bridgeOccupied = false;
if (mutex.hasWaiters(enterLeft)) {
enterLeft.signal();
} else {
enterRight.signal();
}
} finally {
mutex.unlock();
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,61 @@
/*
* Gradle build configuration for specific lab module / exercise
*/
// enabled plugins
plugins {
id 'java'
// Apply the application plugin to add support for building a CLI application.
id 'application'
}
// Project/Module information
description = 'Lab04 CircularBuffer Solution'
group = 'ch.zhaw.prog2'
version = '2022.1'
// Dependency configuration
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.+'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.+'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.+'
testImplementation 'org.mockito:mockito-core:4.3.1'
}
// Test task configuration
test {
// Use JUnit platform for unit tests
useJUnitPlatform()
testLogging {
events "PASSED", "SKIPPED", "FAILED"
}
}
// Configuration for Application plugin
application {
// Define the main class for the application.
mainClass = 'ch.zhaw.prog2.circularbuffer.CircBufferSimulation'
}
// Required to allow System.in.read() when run with gradle.
run {
standardInput = System.in
}
// 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
}
}

View File

@ -0,0 +1,11 @@
package ch.zhaw.prog2.circularbuffer;
public interface Buffer<T> {
boolean put(T element) throws InterruptedException;
T get() throws InterruptedException;
boolean empty();
boolean full();
int count();
void printBufferSlots();
void printBufferContent();
}

View File

@ -0,0 +1,119 @@
package ch.zhaw.prog2.circularbuffer;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class CircBufferSimulation {
public static void main(String[] args) {
final int capacity = 15; // Number of buffer items
final int prodCount = 1; // Number of producer threads
final int consCount = 1; // Number of consumer threads
final int maxProdTime = 500; // max. production time for one item
final int maxConsTime = 500; // max. consumption time for one item
try {
Buffer<String> buffer = new GuardedCircularBuffer<>(String.class, capacity);
// start consumers
Consumer[] consumers = new Consumer[consCount];
for (int i = 0; i < consCount; i++) {
consumers[i] = new Consumer("Consumer_" + i, buffer, maxConsTime);
consumers[i].start();
}
// start producers
Producer[] producers = new Producer[prodCount];
for (int i = 0; i < prodCount; i++) {
producers[i] = new Producer("Producer_" + i, buffer, maxProdTime);
producers[i].start();
}
// print live buffer status
ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(2);
// print occupied slots of buffer every second
scheduled.scheduleAtFixedRate(buffer::printBufferSlots, 1, 1, TimeUnit.SECONDS);
// print content of buffer every 5 seconds
scheduled.scheduleAtFixedRate(buffer::printBufferContent, 5,5, TimeUnit.SECONDS);
System.out.println("Press Enter to terminate");
System.in.read();
System.out.println("Shutting down ...");
// shutdown producers
for (Producer producer : producers) {
producer.terminate();
}
// shutdown consumers
for (Consumer consumer : consumers) {
consumer.terminate();
}
// shutdown statistics
scheduled.shutdown();
System.out.println("Simulation ended.");
} catch (Exception logOrIgnore) {
System.out.println(logOrIgnore.getMessage());
}
}
private static class Producer extends Thread {
private volatile boolean running = true;
private final Buffer<String> buffer;
private final int maxProdTime;
public Producer(String name, Buffer<String> buffer, int prodTime) {
super(name);
this.buffer = buffer;
maxProdTime = prodTime;
}
public void terminate() {
running = false;
}
@Override
public void run() {
running = true;
int number = 1;
try {
while (running) {
buffer.put("#" + number++);
sleep((int) (100 + Math.random() * maxProdTime));
}
} catch (InterruptedException ex) {
System.err.println("Interrupted in " + getName() + ": " + ex.getMessage());
}
}
}
private static class Consumer extends Thread {
private volatile boolean running = true;
private final Buffer<String> buffer;
private final int maxConsTime;
public Consumer(String name, Buffer<String> buffer, int consTime) {
super(name);
this.buffer = buffer;
maxConsTime = consTime;
}
public void terminate() {
running = false;
}
@Override
public void run() {
running = true;
try {
while (running) {
buffer.get();
Thread.sleep(100 + (int) (Math.random() * maxConsTime));
}
} catch (InterruptedException ex) {
System.err.println("Interrupted in " + getName() + ": " + ex.getMessage());
}
}
}
}

View File

@ -0,0 +1,102 @@
package ch.zhaw.prog2.circularbuffer;
import java.lang.reflect.Array;
public class CircularBuffer<T> implements Buffer<T> {
private static final char EMPTY = '-';
private static final char FILLED = '*';
private final StringBuffer printout;
private final T[] items;
private int count = 0;
private int insertPosition = 0;
private int outputPosition = 0;
@SuppressWarnings("unchecked")
public CircularBuffer(Class<T> clazz, int bufferSize) {
printout = new StringBuffer();
if (bufferSize <= 1)
bufferSize = 1;
this.items = (T[]) Array.newInstance(clazz, bufferSize);
}
public boolean put(T item) {
if (this.full())
return false;
items[insertPosition] = item;
insertPosition = (insertPosition + 1) % items.length;
count++;
return true;
}
public T get() {
if (empty())
return null;
T item = items[outputPosition];
outputPosition = (outputPosition + 1) % items.length;
count--;
return item;
}
public boolean empty() {
return count == 0;
}
public boolean full() {
return count >= items.length;
}
public int count() {
return count;
}
public void printBufferSlots() {
printout.delete(0, printout.length());
int i = 0;
// from where to where is the buffer filled?
printout.append("filled where: ||");
if (full()) {
for (i = 0; i < items.length; i++)
printout.append(FILLED);
} else {
char c1;
char c2;
if (insertPosition < outputPosition) { // if iPos < oPos, the buffer
// starts with
// filled slots at pos. 0; otherwise it
// starts with empty slots
c1 = FILLED; // * -> filled slot
c2 = EMPTY; // - -> empty slot
} else {
c1 = EMPTY;
c2 = FILLED;
}
for (i = 0; i < outputPosition && i < insertPosition; i++)
printout.append(c1);
for (; i < outputPosition || i < insertPosition; i++)
printout.append(c2);
for (; i < items.length; i++)
printout.append(c1);
}
printout.append("|| how full: || ");
// how full is the buffer generally?
for (i = 0; i < count; i++)
printout.append(FILLED);
for (; i < items.length; i++)
printout.append(EMPTY);
printout.append("||");
System.out.println(printout);
}
public void printBufferContent() {
System.out.println("Anzahl Elemente im Puffer: " + count);
for (int i = 0; i < count; i++) {
int index = (outputPosition + i) % items.length;
System.out.println("index: " + index + " wert: " + items[index]);
}
}
}

View File

@ -0,0 +1,49 @@
package ch.zhaw.prog2.circularbuffer;
public class GuardedCircularBuffer<T> implements Buffer<T> {
private CircularBuffer<T> buffer;
public GuardedCircularBuffer(Class<T> clazz, int bufferSize) {
buffer = new CircularBuffer<>(clazz, bufferSize);
}
public synchronized boolean put(T item) throws InterruptedException {
while (buffer.full()) {
wait();
}
boolean retVal = buffer.put(item);
notifyAll();
return retVal;
}
public synchronized T get() throws InterruptedException {
while (buffer.empty()) {
wait();
}
T item = buffer.get();
notifyAll();
return item;
}
public synchronized void printBufferSlots() {
buffer.printBufferSlots();
}
public synchronized void printBufferContent() {
buffer.printBufferContent();
}
public synchronized boolean empty() {
return buffer.empty();
}
public synchronized boolean full() {
return buffer.full();
}
public synchronized int count() {
return buffer.count();
}
}

View File

@ -0,0 +1,223 @@
package ch.zhaw.prog2.circularbuffer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
class GuardedCircularBufferTest {
private static final int BUFFER_SIZE = 5;
private static final String DEFAULT_ITEM = "Item";
private GuardedCircularBuffer<String> buffer;
@BeforeEach
void setUp() {
buffer = new GuardedCircularBuffer<>(String.class, BUFFER_SIZE);
}
@AfterEach
void tearDown() {
buffer = null;
}
@Test
void testEmpty() {
assertTrue(buffer.empty(), "Must return true if buffer is empty");
assertDoesNotThrow(() -> { buffer.put("Some content"); }, "Must not throw an exception");
assertFalse(buffer.empty(), "Must return false if buffer is not empty");
}
@Test
void testFull() {
assertFalse(buffer.full(), "Must return false if buffer is empty");
for(int num=0; num < BUFFER_SIZE; num++) {
String item = DEFAULT_ITEM + " " + num;
assertDoesNotThrow(() -> { buffer.put(item); }, "Must not throw an exception");
}
assertTrue(buffer.full(), "Must return true if buffer is full");
assertDoesNotThrow(() -> { buffer.get(); }, "Must not throw an exception");
assertFalse(buffer.full(), "Must return false if buffer is not full");
}
@Test
void testCount() {
assertEquals(0, buffer.count(), "Initial should be 0");
for(int num=1; num <= BUFFER_SIZE; num++) {
String item = DEFAULT_ITEM + " " + num;
assertDoesNotThrow(() -> { buffer.put(item); }, "Must not throw an exception");
assertEquals(num, buffer.count());
}
}
@Test
void testSinglePutGet() {
assertTrue(buffer.empty(), "Make sure buffer is empty");
assertDoesNotThrow(() -> { buffer.put(DEFAULT_ITEM); }, "Must not throw an exception");
assertEquals(1, buffer.count());
AtomicReference<String> returnItem = new AtomicReference<>();
assertDoesNotThrow(() -> { returnItem.set(buffer.get()); }, "Must not throw an exception");
assertEquals(DEFAULT_ITEM, returnItem.get());
}
@Test
void testMultiplePutGet() {
assertTrue(buffer.empty(), "Make sure buffer is empty");
// write items
for (int num = 0; num < BUFFER_SIZE; num++) {
String item = DEFAULT_ITEM + " " + num;
assertDoesNotThrow(() -> {
buffer.put(item);
}, "Must not throw an exception");
}
// read items in same order
for (int num = 0; num < BUFFER_SIZE; num++) {
AtomicReference<String> returnItem = new AtomicReference<>();
assertDoesNotThrow(() -> { returnItem.set(buffer.get()); }, "Must not throw an exception");
assertEquals(DEFAULT_ITEM + " " + num, returnItem.get());
}
}
@Test
void testBlockProduce() {
assertTrue(buffer.empty(), "Make sure buffer is empty");
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
executorService.execute(new Producer<String>(buffer, BUFFER_SIZE+1, DEFAULT_ITEM));
TimeUnit.MILLISECONDS.sleep(100);
assertTrue(buffer.full(), "Buffer should be full");
executorService.shutdown();
assertFalse(executorService.awaitTermination(3, TimeUnit.SECONDS), "Executor should be blocking");
} catch (InterruptedException e) {
fail("Interrupted executor", e);
} finally {
executorService.shutdownNow();
}
}
@Test
void testBlockConsume() {
assertTrue(buffer.empty(), "Make sure buffer is empty");
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
executorService.execute(new Consumer<String>(buffer, 2));
TimeUnit.MILLISECONDS.sleep(100);
assertTrue(buffer.empty(), "Buffer should be empty");
executorService.shutdown();
assertFalse(executorService.awaitTermination(3, TimeUnit.SECONDS), "Executor should be blocking");
} catch (InterruptedException e) {
fail("Interrupted executor", e);
} finally {
executorService.shutdownNow();
}
}
@Test
void testProduceThenConsume() {
assertTrue(buffer.empty(), "Make sure buffer is empty");
try {
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new Producer<String>(buffer, BUFFER_SIZE, DEFAULT_ITEM));
TimeUnit.MILLISECONDS.sleep(100);
assertEquals(BUFFER_SIZE, buffer.count(), "Buffer must contain " + BUFFER_SIZE + " items");
Consumer<String> consumer = new Consumer<>(buffer, BUFFER_SIZE);
executorService.execute(consumer);
TimeUnit.MILLISECONDS.sleep(100);
assertEquals(0, buffer.count(), "Buffer must contain 0 items");
Object[] expected = Stream.generate(() -> DEFAULT_ITEM).limit(BUFFER_SIZE).toArray();
assertArrayEquals(expected, consumer.getItems().toArray());
executorService.shutdown();
assertTrue(executorService.awaitTermination(3, TimeUnit.SECONDS), "Timeout shutting down Executor");
} catch (InterruptedException e) {
fail("Interrupted executor", e);
}
}
@Test
void testProduceAndConsume() {
assertTrue(buffer.empty(), "Make sure buffer is empty");
ExecutorService executorService = Executors.newFixedThreadPool(2);
try {
executorService.execute(new Producer<String>(buffer, 10*BUFFER_SIZE, "Item"));
TimeUnit.SECONDS.sleep(1);
Consumer<String> consumer = new Consumer<>(buffer, 10*BUFFER_SIZE);
executorService.execute(consumer);
TimeUnit.SECONDS.sleep(1);
List<String> receivedItems = consumer.getItems();
assertEquals(10*BUFFER_SIZE, receivedItems.size());
Object[] expected = Stream.generate(() -> DEFAULT_ITEM).limit(10*BUFFER_SIZE).toArray();
assertArrayEquals(expected, consumer.getItems().toArray());
executorService.shutdown();
assertTrue(executorService.awaitTermination(3, TimeUnit.SECONDS), "Timeout shutting down Executor");
} catch (InterruptedException e) {
fail("Interrupted executor", e);
} finally {
executorService.shutdownNow();
}
}
private class Producer<T> implements Runnable {
private GuardedCircularBuffer<T> buffer;
private int numItems;
private T item;
public Producer(GuardedCircularBuffer<T> buffer, int numItems, T item) {
this.buffer = buffer;
this.numItems = numItems;
this.item = item;
}
@Override
public void run() {
for (int num = 0; num < numItems; num++) {
try {
buffer.put(item);
} catch (InterruptedException e) {
System.err.println("Interrupted Producer at " + num + ": " + e.getMessage());
}
}
}
}
private class Consumer<T> implements Runnable {
private GuardedCircularBuffer<T> buffer;
private int numItems;
private List<T> items;
public Consumer(GuardedCircularBuffer<T> buffer, int numItems) {
this.buffer = buffer;
this.numItems = numItems;
this.items = new ArrayList<>();
}
public List<T> getItems() {
return items;
}
@Override
public void run() {
for (int num = 0; num < numItems; num++) {
try {
this.items.add(buffer.get());
} catch (InterruptedException e) {
System.err.println("Interrupted Consumer at " + num + ": " + e.getMessage());
}
}
}
}
}

View File

@ -0,0 +1,22 @@
/*
* Gradle build configuration for specific lab module / exercise
* Default declarations can be found in the lab main build configuration (../../gradle.build)
* Declarations in this file extend or override the default values.
*/
// the Java plugin is added by default in the main lab configuration
plugins {
// Apply the application plugin to add support for building a CLI application.
id 'application'
}
description = 'Lab04 TrafficLight Solution'
dependencies {
}
// Configuration for Application plugin
application {
// Define the main class for the application.
mainClass = 'ch.zhaw.prog2.trafficlight.TrafficLightOperation'
}

View File

@ -0,0 +1,37 @@
package ch.zhaw.prog2.trafficlight;
import java.util.Random;
class Car extends Thread {
private final TrafficLight[] trafficLights;
private int pos;
public Car(String name, TrafficLight[] trafficLights) {
super(name);
this.trafficLights = trafficLights;
pos = 0; // start at first light
start();
}
public synchronized int position() {
return pos;
}
private void gotoNextLight() {
pos = ++pos % trafficLights.length;
}
@Override
public void run() {
Random random = new Random();
while (true) {
try {
sleep(random.nextInt(500));
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
trafficLights[pos].passby();
gotoNextLight();
}
}
}

View File

@ -0,0 +1,28 @@
package ch.zhaw.prog2.trafficlight;
class TrafficLight {
private boolean red;
public TrafficLight() {
red = true;
}
public synchronized void passby() {
while (red) {
try {
wait();
} catch (InterruptedException logOrIgnore) {
System.out.println(logOrIgnore.getMessage());
}
}
}
public synchronized void switchToRed() {
red = true;
}
public synchronized void switchToGreen() {
red = false;
notifyAll();
}
}

View File

@ -0,0 +1,67 @@
package ch.zhaw.prog2.trafficlight;
public class TrafficLightOperation {
private static volatile boolean running = true;
public static void terminate () {
running = false;
}
public static void main(String[] args) {
TrafficLight[] trafficLights = new TrafficLight[7];
Car[] cars = new Car[20];
for (int i = 0; i < trafficLights.length; i++)
trafficLights[i] = new TrafficLight();
for (int i = 0; i < cars.length; i++) {
cars[i] = new Car("Car " + i, trafficLights);
}
// Simulation
while (running) {
for (int greenIndex = 0; greenIndex < trafficLights.length; greenIndex = greenIndex + 2) {
// Display state of simulation
System.out.println("=====================================================");
for (int j = 0; j < trafficLights.length; j++) {
String lightState;
if (j == greenIndex || j == greenIndex + 1)
lightState = "";
else
lightState = "🛑";
System.out.print(lightState + " at Light " + j + ":");
for (int carNumber = 0; carNumber < cars.length; carNumber++) {
if (cars[carNumber].position() == j)
System.out.print(" " + carNumber);
}
System.out.println();
}
System.out.println("=====================================================");
try {
Thread.sleep(3000);
} catch (InterruptedException logOrIgnore) {
System.out.println(logOrIgnore.getMessage());
}
trafficLights[greenIndex].switchToGreen();
if (greenIndex + 1 < trafficLights.length) {
trafficLights[greenIndex + 1].switchToGreen();
}
// green period
try {
Thread.sleep((int) (Math.random() * 500));
} catch (InterruptedException logOrIgnore) {
System.out.println(logOrIgnore.getMessage());
}
trafficLights[greenIndex].switchToRed();
if (greenIndex + 1 < trafficLights.length)
trafficLights[greenIndex + 1].switchToRed();
// red period
try {
Thread.sleep(1000);
} catch (InterruptedException logOrIgnore) {
System.out.println(logOrIgnore.getMessage());
}
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1006 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -0,0 +1,413 @@
:source-highlighter: coderay
:icons: font
:experimental:
:!sectnums:
:imagesdir: ./images/
:handout: ./
: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 Cooperation
:sectnums:
:sectnumlevels: 2
// Beginn des Aufgabenblocks
== Concurrency 3 -- Thread Synchronisation
=== Konto-Übertrag [PU]
[loweralpha]
. Was stellen Sie fest, wenn Sie die Simulation laufen lassen?
Erklären Sie wie die Abweichungen zustande kommen.
+
****
Bei den Transaktionen passieren sog. _lost updates_. Eine Transaktion ist nicht atomar und besteht aus mehreren Schritten (Wert aus Speicher lesen, verändern, Wert in Speicher schreiben).
Durch die gleichzeitige Operation auf den Konten aus mehreren Threads können einzelne dieser Schritte ignoriert werden und verloren gehen.
Beide Threads Lesen den gleichen Wert, aktualisieren diesen gleichzeitig, schreiben das neue Resultat (der letzte gewinnt).
Am Schluss ist die Geldsumme über alle drei Konten oft grösser oder kleiner als am Anfang.
****
. Im Unterricht haben Sie gelernt, dass kritische Bereiche Ihres Codes durch Mutual-Exclusion geschützt werden sollen.
Wie macht man das in Java?
+
Versuchen Sie mittels von Mutual-Exclusion sicherzustellen, dass keine Abweichungen entstehen.
+
** Reicht es die kritischen Bereiche in Account zu sichern?
** Welches sind die kritischen Bereiche?
+
****
Siehe Klasse: `Account`
Monitor Objekte müssen so gewählt werden, dass sie die 'geteilten' Ressourcen schützen. Oft ist deshalb
die geteilte Ressource selber das Monitor Objekt. +
In diesem Fall wären es die `Account`-Objekte auf welche die von verschiedenen Threads zugegriffen wird. +
Den Thread selber als Monitor zu verwenden macht wenig Sinn, da dann jeder Thread seinen eigenen Monitor besitzen würde.
[NOTE]
Der Monitor von Thread-Objekten wird intern für `Thread.join()` verwendet. Beim Beenden eines Threads werden mit `notifyAll()` alle in einem `join()` wartenden Threads informiert. Deshalb sollte auf Thread-Instanzen niemals `wait()`, `notify()` oder `notifyAll()` verwendet werden. Siehe https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/lang/Thread.html#join(long,int)[JavaDoc von Thread.join()].
Kritische Bereiche sind diejenigen, in welchen auf eine geteilte Variable zugegriffen wird.
Das kann sowohl schreibend, wie auch lesend erfolgen. Auch lesende Zugriffe sollten geschützt werden, da sonst nicht sichergestellt werden kann, dass der Wert während des Lesens nicht von einem anderen Thread verändert wird. +
In `Account` sollte deshalb sowohl die Methode `setBalance(int amount)` wie auch `getBalance()` als `synchronized` deklariert werden.
****
+
Untersuchen Sie mehrere Varianten von Locks (Lock auf Methode oder Block,
Lock auf Instanz oder Klasse).
+
Ihre Implementierung muss noch nebenläufige Transaktionen erlauben, d.h. wenn
Sie zu stark synchronisieren, werden alle Transaktionen in Serie ausgeführt und
Threads ergeben keinen Sinn mehr.
+
Stellen Sie für sich folgende Fragen:
+
** Welches ist das Monitor-Objekt?
** Braucht es eventuell das Lock von mehr als einem Monitor, damit eine Transaktion ungestört ablaufen kann?
+
****
Siehe Klasse: `AccountTransferTask`
Auch in `AccountTransferTask` ist immer noch `Account` das Monitor-Objekt, da dort die Daten effektiv verändert werden. Da in `transfer()` jedoch gleichzeitig mit zwei Konten gearbeitet wird, sollte sichergestellt werden, dass nur ein Thread auf exakt die beiden Konten Zugriff hat, damit die Überprüfung und Abbuchung von einem Konto und der Übertrag auf das Andere als eine atomare Funktion erfolgt.
Das heißt wir erhalten einen neuen _kritischen Bereich_ den wir mit synchronized schützen müssen.
Diesmal in dem der Monitor von `fromAccount` und `toAccount` im Voraus schon akquiriert und somit sicherstellt wird, dass `transferAmmount` von beiden Konten nicht mehr blockieren kann. +
In Java kann das mit einer geschachtelten Synchronisation erfolgen:
[source, Java]
----
synchronized(fromAccount) {
synchronized(toAccount) {
if (fromAccount.getBalance() >= amount) {
fromAccount.transferAmount(-amount);
toAccount.transferAmount(amount);
}
}
}
----
Warum kann man nicht einfach die Methode `transfer()` synchronized deklarieren? +
Damit würde die `AccountTransferTask`-Instanz selber als drittes Monitor-Objekt verwendet.
Damit werden nur die Threads synchronisiert, welche das gleiche Task-Objekt verwenden.
Da drei Task-Objekte erstellt werden, könnten zum Beispiel je ein Thread für `task2` und `task3` gleichzeitig auf `account2` zugreifen.
Würde für jede Transaktion sogar eine eigene `AccountTransferTask`-Instanz erzeugt, hätte das synchronized auf `transfer()` überhaupt keinen Effekt mehr, da dann jeder Thread seinen eigenen Monitor besitzt.
****
. Wenn Sie es geschafft haben die Transaktion thread-safe zu implementieren,
ersetzen Sie in `AccountTransferSimulation` die folgende Zeile: +
`AccountTransferTask task1 = new AccountTransferTask(account**3**, account**1**, 2);` +
durch +
`AccountTransferTask task1 = new AccountTransferTask(account**1**, account**3**, 2);` +
und starten Sie das Programm noch einmal.
Was stellen Sie fest? (evtl. müssen Sie es mehrfach versuchen, damit der Effekt auftritt). +
Was könnte die Ursache sein und wie können Sie es beheben? +
[NOTE]
Falls Sie die Frage noch nicht beantworten können, kommen sie nach der Vorlesung "Concurrency 4" nochmals auf diese Aufgabe zurück und versuchen Sie sie dann zu lösen.
+
****
Durch die Umstellung kann es zu einem zirkulären Deadlock kommen, da sich die Threads gegenseitig blockieren (jeder hat ein Konto bereits gelockt und wartet auf ein anderes).
Die einfachste Lösung ist es die Konten immer in einer übergeordneten festen Reihenfolge zu akquirieren.
In diesem Fall zum Beispiel immer zuerst die tiefere Kontonummer.
Dadurch kann es keine zirkulären Abhängigkeiten mehr geben.
Lösung: `AccountTransferTask.transferDLfree()`
****
=== Traffic Light [PU]
In dieser Aufgabe sollen Sie die Funktionsweise einer Ampel und deren Nutzung nachahmen.
Benutzen Sie hierzu die Vorgabe im Modul `TrafficLight`.
[loweralpha]
. Ergänzen Sie zunächst in der Klasse `TrafficLight` drei Methoden:
* Eine Methode zum Setzen der Ampel auf "rot".
* Eine Methode zum Setzen der Ampel auf "grün".
* Eine Methode mit dem Namen `passby()`. Diese Methode soll das Vorbeifahren
eines Fahrzeugs an dieser Ampel nachbilden: Ist die Ampel rot, so wird der
aufrufende Thread angehalten, und zwar so lange, bis die Ampel grün wird.
Ist die Ampel dagegen grün, so kann der Thread sofort aus der Methode zurückkehren,
ohne den Zustand der Ampel zu verändern. Verwenden Sie `wait`, `notify` und
`notifyAll` nur an den unbedingt nötigen Stellen!
+
[NOTE]
Die Zwischenphase „gelb“ spielt keine Rolle Sie können diesem Zustand ignorieren!
+
****
Lösung siehe: `ch.zhaw.prog2.trafficlight.TrafficLight`
****
. Erweitern Sie nun die Klasse `Car` (abgeleitet von `Thread`). +
Im Konstruktor wird eine Referenz auf ein Feld von Ampeln übergeben.
Diese Referenz wird in einem entsprechenden Attribut der Klasse `Car` gespeichert.
In der `run`-Methode werden alle Ampeln dieses Feldes passiert, und zwar in einer Endlosschleife (d.h. nach dem Passieren der letzten Ampel des Feldes wird wieder die erste Ampel im Feld passiert). +
Natürlich darf das Auto erst dann eine Ampel passieren, wenn diese auf grün ist! +
Für die Simulation der Zeitspanne für das Passieren des Signals sollten Sie folgende Anweisung verwenden: `sleep\((int)(Math.random() * 500));`
+
****
Lösung Siehe: `ch.zhaw.prog2.trafficlight.Car`
****
Beantworten Sie entweder (c) oder (d) (nicht beide):
[loweralpha, start=3]
. Falls Sie bei der Implementierung der Klasse TrafficLight die Methode
`notifyAll()` benutzt haben: +
Hätten Sie statt `notifyAll` auch die Methode `notify` verwenden können, oder haben Sie `notifyAll()` unbedingt gebraucht?
Begründen Sie Ihre Antwort!
+
****
Mit `notifyAll()` erhalten alle Autos die Gelegenheit die Ampel zu überqueren.
Natürlich betreten sie den Monitor (`TrafficLight`) immer noch einzeln und können, falls die Zeitspanne nicht, reicht trotzdem stehen bleiben. Dann erhalten Sie bei der nächsten "Grünen Welle" erneut eine Notifikation und können es nochmals probieren.
Bei `notify()` würde immer nur ein Auto an der Ampel losfahren. +
Man könnte das beheben, indem jedes Auto vor dem Losfahren noch ein weiteres `notify()` erzeugt.
Das ist aber eher ein work-around und nicht sehr schön.
****
. Falls Sie bei der Implementierung der Klasse Ampel die Methode `notify()` benutzt
haben: +
Begründen Sie, warum `notifyAll()` nicht unbedingt benötigt wird!
+
****
Siehe oben.
****
. Testen Sie das Programm `TrafficLightOperation.java`.
Die vorgegebene Klasse implementiert eine primitive Simulation von Autos, welche die Ampeln passieren.
Studieren Sie den Code dieser Klasse und überprüfen Sie, ob die erzeugte Ausgabe sinnvoll ist.
=== Producer-Consumer Problem [PU]
In dieser Aufgabe wird ein Producer-Consumer Beispiel mittels einer Queue umgesetzt.
Dazu wird eine Implementation mittels eines link:https://en.wikipedia.org/wiki/Circular_buffer[Digitalen Ringspeichers] umgesetzt.
.Circular Buffer [Wikipedia]
[link = https://en.wikipedia.org/wiki/Circular_buffer]
image::Circular_Buffer_Animation.gif[pdfwidth=75%, width=600px]
Hierzu sind zwei Klassen (`CircularBuffer.java`, `Buffer.java`) vorgegeben, mit folgendem Design:
.Circular Buffer Design
image::CircularBuffer.png[pdfwidth=75%, width=600px]
==== Analyse der abgegebenen CircularBuffer Umsetzung.
Mit dem Testprogramm `CircBufferSimulation` kann die Funktion der `CircularBuffer` Implementierung analysiert werden.
Der mitgelieferte `Producer`-Thread generiert kontinuierlich Daten (in unserem Fall aufsteigende Nummern) und füllt diese mit `buffer.put(...)` in den Buffer.
Der `Consumer`-Thread liest die Daten kontinuierlich mit `buffer.get()` aus dem Buffer aus.
Beide Threads benötigen eine gewisse Zeit zum Produzieren bzw. Konsumieren der Daten.
Diese kann über die Variablen `maxProduceTime` bzw. `maxConsumeTime` beeinflusst werden.
Es können zudem mehrere Producer- bzw. Consumer-Threads erzeugt werden.
[loweralpha]
. Starten Sie `CircularBufferSimulation` und analysieren Sie die Ausgabe.
Der Status des Buffers (belegte Positionen und Füllstand) wird sekündlich ausgegeben.
Alle fünf Sekunden wird zudem der aktuelle Inhalt des Buffers ausgegeben. +
** Wie ist das Verhalten des `CircularBuffer` bei den Standard-Testeinstellungen?
+
****
`Producer` und `Consumer` arbeiten etwa im gleichen Rhythmus.
Das heißt der Buffer ist immer leicht gefüllt, aber nie ganz voll oder leer.
****
. Analysieren Sie die Standard-Einstellungen in `CircularBufferSimulation`.
** Welche Varianten gibt es, die Extrempositionen (Buffer leer, bzw. Buffer voll) zu erzeugen?
+
****
Damit der Buffer immer etwa gleich gefüllt ist und alles reibungslos funktioniert muss
`prodCount * maxProducetime ≈ consCount * maxConsumeTime` sein.
Das heißt es wird gleichviel produziert, wie konsumiert.
**Buffer leer** → es wird mehr konsumiert als produziert
* Mehr Zeit für Produktion: `maxProduceTime` > `maxConsumeTime` setzen.
* Mehr Konsumenten als Produzenten: `prodCount` < `consCount`
**Buffer voll** → es wird mehr produziert als konsumiert
* Mehr Zeit für Konsumation: `maxProduceTime` < `maxConsumeTime` setzen.
* Mehr Produzenten als Konsumenten: `prodCount` > `consCount`
****
** Was ist das erwartete Verhalten bei vollem bzw. leerem Buffer? (bei Producer bzw. Consumer)
+
****
**Buffer leer** → `Consumer` muss warten, bis wieder Daten vorhanden sind. +
**Buffer voll** → `Producer` muss warten, bis wieder Platz für Daten vorhanden ist
****
. Testen Sie was passiert, wenn der Buffer an die Kapazitätsgrenze kommt. Passen Sie dazu die Einstellungen in `CircularBufferSimulation` entsprechend an. +
[TIP]
Belassen sie die Anzahl Producer-Threads vorerst auf 1, damit der Inhalt des Buffers (Zahlenfolge) einfacher verifiziert werden kann.
+
** Was Stellen Sie fest? Was passiert wenn der Buffer voll ist? Warum?
+
****
Damit es einfacher verfolgt werden kann, sollte nur `maxProduceTime` verkleinert bzw. `maxConsumeTime` vergrössert werden.
Sobald der Buffer voll ist, werden die neue produzierten Daten _ignoriert_; d.h. sozusagen weggeworfen.
Das ist gut an den Lücken in der Zahlenfolge im Buffer zu erkennen.
****
. Wiederholen Sie das Gleiche für einen leeren Buffer. Passen Sie die Einstellungen so an, dass der Buffer sicher leer wird, d.h. der `Consumer` keine Daten vorfindet.
** Was stellen Sie fest, wenn der Buffer leer ist? Warum? +
[TIP]
Geben Sie gegebenenfalls die gelesenen Werte des Konsumenten-Threads aus.
+
****
Hierzu muss die `maxProduceTime` vergrössert bzw. `maxConsumeTime` verringert werden.
Damit man den Effekt sehen kann, muss im `Consumer` der Inhalt des konsumierten Strings ausgegeben werden.
Sie stellen fest, dass `null`-Werte geliefert werden, sobald der Buffer leer ist.
Es werden also _Fake-Daten_ konsumiert bzw. weiterverarbeitet.
****
==== Thread-Safe Circular Buffer
In der vorangehenden Übung griffen mehrere Threads (`Producer` & `Consumer`) auf den gleichen Buffer zu.
Die Klasse `CircularBuffer` ist aber nicht thread-safe.
Deshalb soll jetzt eine Wrapper Klasse geschrieben werden, welche die CircularBuffer-Klasse "thread-safe" macht.
Das führt zu folgendem Design:
.Guarded Circular Buffer Design
image::GuardedCircularBuffer.png[pdfwidth=75%, width=600px]
[NOTE]
====
Beachten Sie, dass es sich hier um einen Wrapper (keine Vererbung) handelt. +
Der `GuardedCircularBuffer` hält eine Referenz auf ein `CircularBuffer`-Objekt welches er im Hintergrund für die Speicherung verwendet. Das heißt, viele Methodenaufrufe werden einfach an das gekapselte Objekt weitergeleitet. Einzelne Methoden werden jedoch in ihrer Funktion erweitert. Man spricht auch von "Dekorieren" des Original-Objektes (siehe link:{decorator-pattern}[Decorator-Pattern]).
====
:decorator-pattern: https://en.wikipedia.org/wiki/Decorator_pattern
[loweralpha, start=5]
. Ergänzen Sie die vorhandene Klasse `GuardedCircularBuffer` sodass:
** Die Methoden `empty`, `full`, `count` das korrekte Resultat liefern.
** Aufrufe von `put` blockieren, solange der Buffer voll ist, d.h. bis mindestens wieder ein leeres Buffer-Element vorhanden ist.
** Analog dazu Aufrufe von `get` blockieren, solange der Buffer leer ist, d.h, bis mindestens ein Element im Buffer vorhanden ist.
[TIP]
====
Verwenden Sie den Java Monitor des `GuardedCircularBuffer`-Objektes!
Wenn die Klasse fertig implementiert ist, soll sie in der `CircBufferSimulation` Klasse verwendet werden.
====
****
Siehe Lösung: `ch.zhaw.prog2.circularbuffer.GuardedCircularBuffer`
Als erstes müssen sicher die Methoden `get` und `put` als `synchronized` deklariert werden.
Da jedoch auch die anderen Methoden auf den Status des Buffers zugreifen, müssen auch diese `synchronized` sein.
Bei `get` und `put` wird jeweils in einer `while`-Schleife der Zustand überprüft und falls nicht erfüllt (`get` → buffer leer, `put` → buffer voll) mit `wait()` gewartet.
Sobald ein Element hinzugefügt bzw. gelesen wurde, werden die wartenden `Consumer` oder `Producer` Threads mit `notify()/notifyAll()` benachrichtigt.
Die eigentlichen Operationen werden auf einem gekapselten `CircularBuffer`-Objekt ausgeführt, welches im Konstruktor erzeugt wird.
****
Beantworten Sie entweder (i) oder (ii) (nicht beide):
[lowerroman]
. Falls Sie bei der Implementierung der Klasse `GuardedCircularBuffer` die Methode `notifyAll()` benutzt haben:
Hätten Sie statt `notifyAll()` auch die Methode `notify()` verwenden können oder haben Sie `notifyAll()` unbedingt
benötigt? Begründen Sie Ihre Antwort!
. Falls Sie bei der Implementierung der Klasse `GuardedCircularBuffer` die Methode `notify()` benutzt haben:
Begründen Sie, warum Sie `notifyAll()` nicht unbedingt benötigten!
****
Da bei vernünftiger Dimensionierung des Buffers & der Anzahl Threads, sollten jeweils nur entweder Produzenten oder Konsumenten am Warten sein.
Dann reicht beim Entfernen bzw. Hinzufügen eines Elementes ein `notify()`.
Es wird dann einer der wartenden Threads aufgeweckt, der ein Element hinzufügen bzw. entfernen kann.
Beim nächsten Hinzufügen bzw. Entfernen wird ja wieder eine neue Notifikation erzeugt.
Wenn der Buffer aber sehr klein und die Zahl der Produzenten / Konsumenten gross ist, könnte der Fall auftreten, dass sowohl Konsumenten, wie auch Produzenten am Warten sind.
Um sicherzugehen, dass sicher ein Produzent bzw. Konsument zum Zug kommt, wecken wir deshalb am besten alle wartenden Threads.
Das hat aber den Nachteil, dass alle ihre Bedingung überprüfen müssen, obwohl nur der erste passende Thread zum Zuge kommt, da ja nur ein Platz frei wurde, der gefüllte bzw. ein Element vorhanden ist, das abgeholt werden kann.
****
== Concurrency 4 -- Lock & Conditions, Deadlocks
=== Single-Lane Bridge [PU]
Die Brücke über einen Fluss ist so schmal, dass Fahrzeuge nicht kreuzen können.
Sie soll jedoch von beiden Seiten überquert werden können.
Es braucht somit eine Synchronisation, damit die Fahrzeuge nicht kollidieren.
Um das Problem zu illustrieren wird eine fehlerhaft funktionierende Anwendung,
in welcher keine Synchronisierung vorgenommen wird, zur Verfügung gestellt.
Ihre Aufgabe ist es, die Synchronisation der Fahrzeuge einzubauen.
Die Anwendung finden Sie im link:{handout}[Praktikumsverzeichnis] im Modul `Bridge`.
Gestartet wird sie indem die Klasse `Main` ausgeführt wird (z.B. mit `gradle run`).
Das GUI sollte selbsterklärend sein.
Mit den zwei Buttons können sie Autos links bzw. rechts hinzufügen. Sie werden feststellen, dass die Autos auf der Brücke kollidieren, bzw. illegalerweise durcheinander hindurchfahren.
.Single-Lane Bridge GUI
image::bridge_overview.png[pdfwidth=75%, width=600px]
Um das Problem zu lösen, müssen Sie die den GUI Teil der Anwendung nicht verstehen.
Sie müssen nur wissen, dass Fahrzeuge, die von links nach rechts fahren, die Methode `controller.enterLeft()` aufrufen bevor sie auf die Brücke fahren (um Erlaubnis fragen) und die Methode `controller.leaveRight()` aufrufen, sobald sie die Brücke verlassen.
Fahrzeuge in die andere Richtung rufen entsprechend die Methoden `enterRight()` und `leaveLeft()` auf.
Dabei ist `controller` eine Instanz der Klasse `TrafficController`, welche für die Synchronisation zuständig ist.
In der mitgelieferten Klasse sind die vier Methoden nicht implementiert (Dummy-Methoden).
[loweralpha]
. Erweitern sie `TrafficController` zu einer Monitorklasse, die sicherstellt, dass die Autos nicht mehr kollidieren.
Verwenden Sie dazu den Lock und Conditions Mechanismus.
[TIP]
Verwenden Sie eine Statusvariable, um den Zustand der Brücke zu repräsentieren (e.g. `boolean bridgeOccupied`).
+
****
Siehe Code: `ch.zhaw.prog2.bridge.TrafficControllerA`
****
. Passen Sie die Klasse `TrafficController` so an, dass jeweils abwechslungsweise ein Fahrzeug von links und rechts die Brücke passieren kann.
Unter Umständen wird ein Auto blockiert, wenn auf der anderen Seite keines mehr wartet.
Verwenden Sie für die Lösung mehrere Condition Objekte.
[NOTE]
Um die Version aus a. zu behalten, können sie auch eine Kopie (z.B. `TrafficControllerB`) erzeugen und `Main` anpassen, damit eine Instanz dieser Klasse verwendet wird.
+
****
Siehe Code: `ch.zhaw.prog2.bridge.TrafficControllerB`
****
. Bauen Sie die Klasse `TrafficController` so um, dass jeweils alle wartenden Fahrzeuge aus einer Richtung passieren können.
Erst wenn keines mehr wartet, kann die Gegenrichtung fahren.
[TIP]
Mit link:{ReentrantLock}[`ReentrentLock.hasWaiters(Condition c)`] können Sie
abfragen ob Threads auf eine bestimmte Condition warten.
+
****
Siehe Code: `ch.zhaw.prog2.bridge.TrafficControllerC`
****
:ReentrantLock: https://docs.oracle.com/en/java/javase/16/docs/api/java.base/java/util/concurrent/locks/ReentrantLock.html#hasWaiters(java.util.concurrent.locks.Condition)
=== The Dining Philosophers [PA]
****
Die Lösungen zu den bewerteten Pflichtaufgaben erhalten Sie nach der Abgabe und Bewertung aller Klassen.
****
// Ende des Aufgabenblocks
:!sectnums:
// == Aufräumarbeiten

File diff suppressed because one or more lines are too long