Initial commit

This commit is contained in:
github-classroom[bot]
2022-03-31 09:49:56 +00:00
commit 5a1de0b8e7
83 changed files with 17003 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
/*
* Gradle build configuration for specific lab module / exercise
* Default declarations can be found in the lab main build configuration (../../gradle.build)
* Declarations in this file extend or override the default values.
*/
// the Java plugin is added by default in the main lab configuration
plugins {
// Apply the application plugin to add support for building a CLI application.
id 'application'
}
description = 'Lab04 AccountTransfer'
dependencies {
}
// Configuration for Application plugin
application {
// Define the main class for the application.
mainClass = 'ch.zhaw.prog2.account.AccountTransferSimulation'
}
@@ -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;
}
}
}
+22
View File
@@ -0,0 +1,22 @@
/*
* Gradle build configuration for specific lab module / exercise
* Default declarations can be found in the lab main build configuration (../../gradle.build)
* Declarations in this file extend or override the default values.
*/
// the Java plugin is added by default in the main lab configuration
plugins {
// Apply the application plugin to add support for building a CLI application.
id 'application'
}
description = 'Lab04 Bridge'
dependencies {
}
// Configuration for Application plugin
application {
// Define the main class for the application.
mainClass = 'ch.zhaw.prog2.bridge.Main'
}
@@ -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() {}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

+63
View File
@@ -0,0 +1,63 @@
/*
* Gradle build configuration for specific lab module / exercise
* Default declarations can be found in the lab main build configuration (../../gradle.build)
* Declarations in this file extend or override the default values.
*/
// enabled plugins
plugins {
id 'java'
// Apply the application plugin to add support for building a CLI application.
id 'application'
}
// Project/Module information
description = 'Lab04 CircularBuffer'
group = 'ch.zhaw.prog2'
version = '2022.1'
// Dependency configuration
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.+'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.+'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.+'
testImplementation 'org.mockito:mockito-core:4.3.1'
}
// Test task configuration
test {
// Use JUnit platform for unit tests
useJUnitPlatform()
testLogging {
events "PASSED", "SKIPPED", "FAILED"
}
}
// Configuration for Application plugin
application {
// Define the main class for the application.
mainClass = 'ch.zhaw.prog2.circularbuffer.CircBufferSimulation'
}
// Required to allow System.in.read() when run with gradle.
run {
standardInput = System.in
}
// Java plugin configuration
java {
// By default the Java version of the gradle process is used as source/target version.
// This can be overridden, to ensure a specific version. Enable only if required.
// sourceCompatibility = JavaVersion.VERSION_17 // ensure Java source code compatibility
// targetCompatibility = JavaVersion.VERSION_17 // version of the created byte-code
// Java compiler specific options
compileJava {
// source files should be UTF-8 encoded
options.encoding = 'UTF-8'
// for more options see https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.CompileOptions.html
}
}
@@ -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());
}
}
}
}
}
+56
View File
@@ -0,0 +1,56 @@
/*
* Gradle build configuration for specific lab module / exercise
*/
// enabled plugins
plugins {
id 'java'
// Apply the application plugin to add support for building a CLI application.
id 'application'
}
// Project/Module information
description = 'Lab04 Philosopher'
group = 'ch.zhaw.prog2'
version = '2022.1'
// Dependency configuration
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.+'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.+'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.+'
testImplementation 'org.mockito:mockito-core:4.3.1'
}
// Test task configuration
test {
// Use JUnit platform for unit tests
useJUnitPlatform()
testLogging {
events "PASSED", "SKIPPED", "FAILED"
}
}
// Configuration for Application plugin
application {
// Define the main class for the application.
mainClass = 'ch.zhaw.prog2.philosopher.PhilosopherGui'
}
// Java plugin configuration
java {
// By default the Java version of the gradle process is used as source/target version.
// This can be overridden, to ensure a specific version. Enable only if required.
// sourceCompatibility = JavaVersion.VERSION_17 // ensure Java source code compatibility
// targetCompatibility = JavaVersion.VERSION_17 // version of the created byte-code
// Java compiler specific options
compileJava {
// source files should be UTF-8 encoded
options.encoding = 'UTF-8'
// for more options see https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.CompileOptions.html
}
}
@@ -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";
};
}
}
}
+22
View File
@@ -0,0 +1,22 @@
/*
* Gradle build configuration for specific lab module / exercise
* Default declarations can be found in the lab main build configuration (../../gradle.build)
* Declarations in this file extend or override the default values.
*/
// the Java plugin is added by default in the main lab configuration
plugins {
// Apply the application plugin to add support for building a CLI application.
id 'application'
}
description = 'Lab04 TrafficLight'
dependencies {
}
// Configuration for Application plugin
application {
// Define the main class for the application.
mainClass = 'ch.zhaw.prog2.trafficlight.TrafficLightOperation'
}
@@ -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());
}
}
}
}
}