Initial commit
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
+66
@@ -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() {}
|
||||
}
|
||||
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 |
@@ -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();
|
||||
}
|
||||
+119
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+37
@@ -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;
|
||||
}
|
||||
}
|
||||
+223
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user