Initial commit
|
@ -0,0 +1,29 @@
|
|||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Default formatting Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# do not trim trailing whitespace in markdown files
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
# explicit 4 space indentation
|
||||
[*.py]
|
||||
indent_size = 4
|
||||
|
||||
# explicit 2 space indentation
|
||||
[*.{json, yml, yaml, xml, ddl, sql}]
|
||||
indent_size = 2
|
||||
|
||||
# windows specific files
|
||||
[*.{bat, cmd}]
|
||||
end_of_line = crlf
|
|
@ -0,0 +1,5 @@
|
|||
#
|
||||
# https://help.github.com/articles/dealing-with-line-endings/
|
||||
#
|
||||
# These are explicitly windows files and should use crlf
|
||||
*.bat text eol=crlf
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"tests": [
|
||||
{
|
||||
"name": "Run PA unit tests",
|
||||
"setup": "",
|
||||
"run": "gradle :Philosopher:test --info",
|
||||
"input": "",
|
||||
"output": "",
|
||||
"comparison": "included",
|
||||
"timeout": 10,
|
||||
"points": 2
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
name: GitHub Classroom Workflow
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Autograding
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: '17'
|
||||
- uses: education/autograding@v1
|
|
@ -0,0 +1,70 @@
|
|||
# InelliJ IDEA files
|
||||
*.iml
|
||||
*.ipr
|
||||
*.ids
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# Eclipse files
|
||||
.project
|
||||
.metadata
|
||||
.classpath
|
||||
.settings/
|
||||
.loadpath
|
||||
bin/
|
||||
|
||||
# Netbeans
|
||||
nbactions.xml
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode
|
||||
|
||||
# Maven
|
||||
target/
|
||||
|
||||
# gradle files
|
||||
.gradle
|
||||
build/
|
||||
|
||||
# ignore logfiles
|
||||
*.log*
|
||||
|
||||
# OS dependant files
|
||||
.DS_Store
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
Thumbs.db
|
||||
Desktop.ini
|
||||
*~
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# compiled files
|
||||
*.com
|
||||
*.class
|
||||
*.dll
|
||||
*.exe
|
||||
*.o
|
||||
*.so
|
||||
|
||||
# packages
|
||||
*.7z
|
||||
#*.jar
|
||||
*.rar
|
||||
*.zip
|
||||
*.gz
|
||||
*.bzip
|
||||
*.xz
|
||||
*.lzma
|
||||
*~$*
|
||||
|
||||
# package managment formats
|
||||
*.dmg
|
||||
*.xpi
|
||||
*.gem
|
||||
*.egg
|
||||
*.deb
|
||||
*.rpm
|
||||
|
||||
# databases
|
||||
*.sqlite
|
|
@ -0,0 +1,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.
|
|
@ -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'
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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'
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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() {}
|
||||
}
|
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 1.8 KiB |
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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 + "}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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'
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
|
@ -0,0 +1,234 @@
|
|||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
|
@ -0,0 +1,89 @@
|
|||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
After Width: | Height: | Size: 168 KiB |
After Width: | Height: | Size: 1006 KiB |
After Width: | Height: | Size: 270 KiB |
After Width: | Height: | Size: 121 KiB |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 50 KiB |
After Width: | Height: | Size: 35 KiB |
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Dynamic Multi-Module project structure
|
||||
* automatically adds each exercise as a sub-project (module)
|
||||
*/
|
||||
|
||||
// use current directory name as root project name
|
||||
rootProject.name = file('.').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')
|
|
@ -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'
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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'
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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() {}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 1.8 KiB |
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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'
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 168 KiB |
After Width: | Height: | Size: 1006 KiB |
After Width: | Height: | Size: 270 KiB |
After Width: | Height: | Size: 121 KiB |
After Width: | Height: | Size: 48 KiB |
|
@ -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
|