Initial commit
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Gradle build configuration for specific lab module / exercise
|
||||
* Default declarations can be found in the lab main build configuration (../../gradle.build)
|
||||
* Declarations in this file extend or override the default values.
|
||||
*/
|
||||
// the Java plugin is added by default in the main lab configuration
|
||||
plugins {
|
||||
// Apply the application plugin to add support for building a CLI application.
|
||||
id 'application'
|
||||
}
|
||||
|
||||
description = 'Lab04 AccountTransfer Solution'
|
||||
|
||||
dependencies {
|
||||
|
||||
}
|
||||
|
||||
// Configuration for Application plugin
|
||||
application {
|
||||
// Define the main class for the application.
|
||||
mainClass = 'ch.zhaw.prog2.account.AccountTransferSimulation'
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package ch.zhaw.prog2.account;
|
||||
|
||||
public class Account {
|
||||
private final int id;
|
||||
private int balance = 0;
|
||||
|
||||
public Account(int id, int initialAmount) {
|
||||
this.id = id;
|
||||
this.balance = initialAmount;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public synchronized int getBalance() {
|
||||
return balance;
|
||||
}
|
||||
|
||||
public synchronized void transferAmount(int amount) {
|
||||
this.balance += amount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package ch.zhaw.prog2.account;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class AccountTransferSimulation {
|
||||
|
||||
private static final int ITERATIONS = 10000;
|
||||
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
|
||||
Account account1 = new Account(1, 10);
|
||||
Account account2 = new Account(2, 10);
|
||||
Account account3 = new Account(3, 999999);
|
||||
|
||||
System.out.println("Start Balance:");
|
||||
System.out.println("- Account1 = " + account1.getBalance());
|
||||
System.out.println("- Account2 = " + account2.getBalance());
|
||||
System.out.println("- Account3 = " + account3.getBalance());
|
||||
System.out.println("Summed up balances of all accounts: " +
|
||||
(account1.getBalance() + account2.getBalance() + account3.getBalance()));
|
||||
System.out.println("Start of Transaction");
|
||||
|
||||
// AccountTransferTask task1 = new AccountTransferTask(account3, account1, 2);
|
||||
AccountTransferTask task1 = new AccountTransferTask(account1, account3, 2);
|
||||
AccountTransferTask task2 = new AccountTransferTask(account3, account2, 1);
|
||||
AccountTransferTask task3 = new AccountTransferTask(account2, account1, 2);
|
||||
|
||||
// create ExecutorService
|
||||
ExecutorService executor = Executors.newFixedThreadPool(8);
|
||||
|
||||
// execute transactions on accounts
|
||||
System.out.println("Submitting...");
|
||||
for (int count = 0; count < ITERATIONS; count++) {
|
||||
executor.execute(task1);
|
||||
executor.execute(task2);
|
||||
executor.execute(task3);
|
||||
}
|
||||
System.out.println("Working...");
|
||||
|
||||
executor.shutdown();
|
||||
// wait for completion
|
||||
boolean completed = executor.awaitTermination(20, TimeUnit.SECONDS);
|
||||
|
||||
if (completed) {
|
||||
System.out.println("Transactions completed!");
|
||||
} else {
|
||||
System.out.println("Transactions timed out!");
|
||||
executor.shutdownNow();
|
||||
|
||||
}
|
||||
|
||||
// Print end statistics
|
||||
System.out.println("Amount transferred (when enough on fromAccount):");
|
||||
System.out.println("- Task 1 = " + task1.getTotalTransfer());
|
||||
System.out.println("- Task 2 = " + task2.getTotalTransfer());
|
||||
System.out.println("- Task 3 = " + task3.getTotalTransfer());
|
||||
System.out.println("End Balance:");
|
||||
System.out.println("- Account1 = " + account1.getBalance());
|
||||
System.out.println("- Account2 = " + account2.getBalance());
|
||||
System.out.println("- Account3 = " + account3.getBalance());
|
||||
System.out.println("Summed up balances of all accounts (should be the same as at start): " +
|
||||
(account1.getBalance() + account2.getBalance() + account3.getBalance()));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package ch.zhaw.prog2.account;
|
||||
|
||||
class AccountTransferTask implements Runnable {
|
||||
|
||||
private final Account fromAccount;
|
||||
private final Account toAccount;
|
||||
private final int amount;
|
||||
private int totalTransfer = 0;
|
||||
|
||||
public AccountTransferTask(Account fromAccount, Account toAccount, int amount) {
|
||||
this.fromAccount = fromAccount;
|
||||
this.toAccount = toAccount;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public int getTotalTransfer() {
|
||||
return this.totalTransfer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
//transfer();
|
||||
transferDLfree();
|
||||
}
|
||||
|
||||
/* b) ensure no lost updates are happening */
|
||||
public void transfer() {
|
||||
synchronized (fromAccount) {
|
||||
synchronized (toAccount) {
|
||||
// Account must not be overdrawn
|
||||
if (fromAccount.getBalance() >= amount) {
|
||||
fromAccount.transferAmount(-amount);
|
||||
toAccount.transferAmount(amount);
|
||||
totalTransfer += amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* c) deadlock free using ascending order of IDs */
|
||||
public void transferDLfree() {
|
||||
// This implementation is deadlock free using the second possibility
|
||||
// -> locking of accounts in ascending order of accountID
|
||||
boolean isLower = fromAccount.getId() < toAccount.getId();
|
||||
Account lowerAccount = isLower ? fromAccount : toAccount;
|
||||
Account higherAccount = !isLower ? fromAccount : toAccount;
|
||||
synchronized (lowerAccount) {
|
||||
synchronized (higherAccount) {
|
||||
// Account must not be overdrawn
|
||||
if (fromAccount.getBalance() >= amount) {
|
||||
fromAccount.transferAmount(-amount);
|
||||
toAccount.transferAmount(amount);
|
||||
totalTransfer += amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Gradle build configuration for specific lab module / exercise
|
||||
* Default declarations can be found in the lab main build configuration (../../gradle.build)
|
||||
* Declarations in this file extend or override the default values.
|
||||
*/
|
||||
// the Java plugin is added by default in the main lab configuration
|
||||
plugins {
|
||||
// Apply the application plugin to add support for building a CLI application.
|
||||
id 'application'
|
||||
}
|
||||
|
||||
description = 'Lab04 Bridge Solution'
|
||||
|
||||
dependencies {
|
||||
|
||||
}
|
||||
|
||||
// Configuration for Application plugin
|
||||
application {
|
||||
// Define the main class for the application.
|
||||
mainClass = 'ch.zhaw.prog2.bridge.Main'
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package ch.zhaw.prog2.bridge;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
public class Car implements Runnable {
|
||||
|
||||
public static final int REDCAR = 0;
|
||||
public static final int BLUECAR = 1;
|
||||
|
||||
private final int bridgeY = 95;
|
||||
private final int bridgeXLeft = 210;
|
||||
private final int bridgeXLeft2 = 290;
|
||||
private final int bridgeXMid = 410;
|
||||
private final int bridgeXRight2 = 530;
|
||||
private final int bridgeXRight = 610;
|
||||
private final int totalWidth = 900;
|
||||
private final int initX[] = {-80, totalWidth};
|
||||
private final int initY[] = {135, 55};
|
||||
private final int outLeft = -200;
|
||||
private final int outRight = totalWidth + 200;
|
||||
|
||||
int cartype;
|
||||
int xpos, ypos;
|
||||
Car inFront;
|
||||
Image image;
|
||||
TrafficController controller;
|
||||
|
||||
public Car(int cartype, Car inFront, Image image, TrafficController controller) {
|
||||
this.cartype = cartype;
|
||||
this.inFront = inFront;
|
||||
this.image = image;
|
||||
this.controller = controller;
|
||||
if (cartype == REDCAR) {
|
||||
xpos = inFront == null ? outRight : Math.min(initX[cartype], inFront.getX()-90);
|
||||
} else {
|
||||
xpos = inFront == null ? outLeft : Math.max(initX[cartype], inFront.getX()+90);
|
||||
}
|
||||
ypos = initY[cartype];
|
||||
}
|
||||
|
||||
public void move() {
|
||||
int xposOld = xpos;
|
||||
if (cartype==REDCAR) {
|
||||
if (inFront.getX() - xpos > 100) {
|
||||
xpos += 4;
|
||||
if (xpos >= bridgeXLeft && xposOld < bridgeXLeft) controller.enterLeft();
|
||||
else if (xpos > bridgeXLeft && xpos < bridgeXMid) { if (ypos > bridgeY) ypos -= 2; }
|
||||
else if (xpos >= bridgeXRight2 && xpos < bridgeXRight) { if (ypos < initY[REDCAR]) ypos += 2; }
|
||||
else if (xpos >= bridgeXRight && xposOld < bridgeXRight) controller.leaveRight();
|
||||
}
|
||||
} else {
|
||||
if (xpos-inFront.getX() > 100) {
|
||||
xpos -= 4;
|
||||
if (xpos <= bridgeXRight && xposOld > bridgeXRight) controller.enterRight();
|
||||
else if (xpos < bridgeXRight && xpos > bridgeXMid) { if (ypos < bridgeY) ypos += 2; }
|
||||
else if (xpos <= bridgeXLeft2 && xpos > bridgeXLeft) { if(ypos > initY[BLUECAR]) ypos -= 2; }
|
||||
else if (xpos <= bridgeXLeft && xposOld > bridgeXLeft) controller.leaveLeft();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void run() {
|
||||
boolean outOfSight = cartype == REDCAR ? xpos > totalWidth : xpos < -80;
|
||||
while (!outOfSight) {
|
||||
move();
|
||||
outOfSight = cartype == REDCAR ? xpos > totalWidth : xpos < -80;
|
||||
try {
|
||||
Thread.sleep(30);
|
||||
} catch (InterruptedException e) {
|
||||
System.out.println(e.getMessage());
|
||||
}
|
||||
}
|
||||
xpos = cartype == REDCAR ? outRight: outLeft;
|
||||
}
|
||||
|
||||
public int getX() { return xpos; }
|
||||
|
||||
public void draw(Graphics g) {
|
||||
g.drawImage(image, xpos, ypos, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package ch.zhaw.prog2.bridge;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
public class CarWindow extends JFrame {
|
||||
|
||||
public CarWindow(CarWorld world) {
|
||||
Container c = getContentPane();
|
||||
c.setLayout(new BorderLayout());
|
||||
c.add("Center",world);
|
||||
JButton addLeft = new JButton("Add Left");
|
||||
JButton addRight = new JButton("Add Right");
|
||||
addLeft.addActionListener((e) -> world.addCar(Car.REDCAR));
|
||||
addRight.addActionListener((e) -> world.addCar(Car.BLUECAR));
|
||||
|
||||
JPanel p1 = new JPanel();
|
||||
p1.setLayout(new FlowLayout());
|
||||
p1.add(addLeft);
|
||||
p1.add(addRight);
|
||||
c.add("South",p1);
|
||||
setDefaultCloseOperation(EXIT_ON_CLOSE);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package ch.zhaw.prog2.bridge;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
|
||||
class CarWorld extends JPanel {
|
||||
|
||||
Image bridge;
|
||||
Image redCar;
|
||||
Image blueCar;
|
||||
|
||||
TrafficController controller;
|
||||
|
||||
ArrayList<Car> blueCars = new ArrayList<>();
|
||||
ArrayList<Car> redCars = new ArrayList<>();
|
||||
|
||||
public CarWorld(TrafficController controller) {
|
||||
this.controller = controller;
|
||||
MediaTracker mt = new MediaTracker(this);
|
||||
Toolkit toolkit = Toolkit.getDefaultToolkit();
|
||||
|
||||
redCar = toolkit.getImage(getClass().getResource("redcar.gif"));
|
||||
mt.addImage(redCar, 0);
|
||||
blueCar = toolkit.getImage(getClass().getResource("bluecar.gif"));
|
||||
mt.addImage(blueCar, 1);
|
||||
bridge = toolkit.getImage(getClass().getResource("bridge1.gif"));
|
||||
mt.addImage(bridge, 2);
|
||||
|
||||
try {
|
||||
mt.waitForID(0);
|
||||
mt.waitForID(1);
|
||||
mt.waitForID(2);
|
||||
} catch (java.lang.InterruptedException e) {
|
||||
System.out.println("Couldn't load one of the images");
|
||||
}
|
||||
|
||||
redCars.add( new Car(Car.REDCAR,null,redCar,null) );
|
||||
blueCars.add( new Car(Car.BLUECAR,null,blueCar,null) );
|
||||
setPreferredSize( new Dimension(bridge.getWidth(null), bridge.getHeight(null)) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintComponent(Graphics g) {
|
||||
g.drawImage(bridge,0,0,this);
|
||||
for (Car c : redCars) c.draw(g);
|
||||
for (Car c : blueCars) c.draw(g);
|
||||
}
|
||||
|
||||
public void addCar(final int cartype) {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
Car car;
|
||||
if (cartype==Car.REDCAR) {
|
||||
car = new Car(cartype,redCars.get(redCars.size()-1),redCar,controller);
|
||||
redCars.add(car);
|
||||
} else {
|
||||
car = new Car(cartype,blueCars.get(blueCars.size()-1),blueCar,controller);
|
||||
blueCars.add(car);
|
||||
}
|
||||
new Thread(car).start();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package ch.zhaw.prog2.bridge;
|
||||
|
||||
public class Main {
|
||||
|
||||
private static void nap(int ms) {
|
||||
try {
|
||||
Thread.sleep(ms);
|
||||
} catch (InterruptedException e) {
|
||||
System.out.println(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] a) {
|
||||
// final TrafficController controller = new TrafficController();
|
||||
// final TrafficController controller = new TrafficControllerA();
|
||||
// final TrafficController controller = new TrafficControllerB();
|
||||
final TrafficController controller = new TrafficControllerC();
|
||||
|
||||
final CarWorld world = new CarWorld(controller);
|
||||
final CarWindow win = new CarWindow(world);
|
||||
|
||||
win.pack();
|
||||
win.setVisible(true);
|
||||
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
while (true) {
|
||||
nap(25);
|
||||
win.repaint();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package ch.zhaw.prog2.bridge;
|
||||
|
||||
/*
|
||||
* Controls the traffic passing the bridge
|
||||
*/
|
||||
public class TrafficController {
|
||||
|
||||
/* Called when a car wants to enter the bridge form the left side */
|
||||
public void enterLeft() {}
|
||||
|
||||
/* Called when a wants to enter the bridge form the right side */
|
||||
public void enterRight() {}
|
||||
|
||||
/* Called when the car leaves the bridge on the left side */
|
||||
public void leaveLeft() {}
|
||||
|
||||
/* Called when the car leaves the bridge on the right side */
|
||||
public void leaveRight() {}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package ch.zhaw.prog2.bridge;
|
||||
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public class TrafficControllerA extends TrafficController {
|
||||
|
||||
private boolean bridgeOccupied = false;
|
||||
private Lock mutex = new ReentrantLock();
|
||||
private Condition enterBridge = mutex.newCondition();
|
||||
|
||||
public void enterLeft() {
|
||||
mutex.lock();
|
||||
try {
|
||||
while(bridgeOccupied) {
|
||||
enterBridge.await();
|
||||
}
|
||||
bridgeOccupied = true;
|
||||
} catch (InterruptedException e) {
|
||||
System.err.println("Interrupt: " + e.getMessage());
|
||||
} finally {
|
||||
mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void enterRight() {
|
||||
mutex.lock();
|
||||
try {
|
||||
while(bridgeOccupied) {
|
||||
enterBridge.await();
|
||||
}
|
||||
bridgeOccupied = true;
|
||||
} catch (InterruptedException e) {
|
||||
System.err.println("Interrupt: " + e.getMessage());
|
||||
} finally {
|
||||
mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void leaveLeft() {
|
||||
mutex.lock();
|
||||
try {
|
||||
bridgeOccupied = false;
|
||||
enterBridge.signal();
|
||||
} finally {
|
||||
mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void leaveRight() {
|
||||
mutex.lock();
|
||||
try {
|
||||
bridgeOccupied = false;
|
||||
enterBridge.signal();
|
||||
} finally {
|
||||
mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package ch.zhaw.prog2.bridge;
|
||||
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public class TrafficControllerB extends TrafficController {
|
||||
|
||||
private boolean bridgeOccupied = false;
|
||||
private Lock mutex = new ReentrantLock();
|
||||
private Condition enterLeft = mutex.newCondition();
|
||||
private Condition enterRight = mutex.newCondition();
|
||||
|
||||
public void enterLeft() {
|
||||
mutex.lock();
|
||||
try {
|
||||
while(bridgeOccupied) {
|
||||
enterLeft.await();
|
||||
}
|
||||
bridgeOccupied = true;
|
||||
} catch (InterruptedException e) {
|
||||
System.err.println("Interrupt: " + e.getMessage());
|
||||
} finally {
|
||||
mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void enterRight() {
|
||||
mutex.lock();
|
||||
try {
|
||||
while(bridgeOccupied) {
|
||||
enterRight.await();
|
||||
}
|
||||
bridgeOccupied = true;
|
||||
} catch (InterruptedException e) {
|
||||
System.err.println("Interrupt: " + e.getMessage());
|
||||
} finally {
|
||||
mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void leaveLeft() {
|
||||
mutex.lock();
|
||||
try {
|
||||
bridgeOccupied = false;
|
||||
enterLeft.signal(); // wake-up car on opposite side
|
||||
} finally {
|
||||
mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void leaveRight() {
|
||||
mutex.lock();
|
||||
try {
|
||||
bridgeOccupied = false;
|
||||
enterRight.signal(); // wake-up car on opposite side
|
||||
} finally {
|
||||
mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package ch.zhaw.prog2.bridge;
|
||||
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public class TrafficControllerC extends TrafficController {
|
||||
|
||||
private boolean bridgeOccupied = false;
|
||||
private ReentrantLock mutex = new ReentrantLock();
|
||||
private Condition enterLeft = mutex.newCondition();
|
||||
private Condition enterRight = mutex.newCondition();
|
||||
|
||||
public void enterLeft() {
|
||||
mutex.lock();
|
||||
try {
|
||||
while(bridgeOccupied) {
|
||||
enterLeft.await();
|
||||
}
|
||||
bridgeOccupied = true;
|
||||
} catch (InterruptedException e) {
|
||||
System.err.println("Interrupt: " + e.getMessage());
|
||||
} finally {
|
||||
mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void enterRight() {
|
||||
mutex.lock();
|
||||
try {
|
||||
while(bridgeOccupied) {
|
||||
enterRight.await();
|
||||
}
|
||||
bridgeOccupied = true;
|
||||
} catch (InterruptedException e) {
|
||||
System.err.println("Interrupt: " + e.getMessage());
|
||||
} finally {
|
||||
mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void leaveLeft() {
|
||||
mutex.lock();
|
||||
try {
|
||||
bridgeOccupied = false;
|
||||
if (mutex.hasWaiters(enterRight)) {
|
||||
enterRight.signal();
|
||||
} else {
|
||||
enterLeft.signal();
|
||||
}
|
||||
} finally {
|
||||
mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void leaveRight() {
|
||||
mutex.lock();
|
||||
try {
|
||||
bridgeOccupied = false;
|
||||
if (mutex.hasWaiters(enterLeft)) {
|
||||
enterLeft.signal();
|
||||
} else {
|
||||
enterRight.signal();
|
||||
}
|
||||
} finally {
|
||||
mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Gradle build configuration for specific lab module / exercise
|
||||
*/
|
||||
// enabled plugins
|
||||
plugins {
|
||||
id 'java'
|
||||
// Apply the application plugin to add support for building a CLI application.
|
||||
id 'application'
|
||||
}
|
||||
|
||||
// Project/Module information
|
||||
description = 'Lab04 CircularBuffer Solution'
|
||||
group = 'ch.zhaw.prog2'
|
||||
version = '2022.1'
|
||||
|
||||
// Dependency configuration
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.+'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.+'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.+'
|
||||
testImplementation 'org.mockito:mockito-core:4.3.1'
|
||||
}
|
||||
|
||||
// Test task configuration
|
||||
test {
|
||||
// Use JUnit platform for unit tests
|
||||
useJUnitPlatform()
|
||||
testLogging {
|
||||
events "PASSED", "SKIPPED", "FAILED"
|
||||
}
|
||||
}
|
||||
|
||||
// Configuration for Application plugin
|
||||
application {
|
||||
// Define the main class for the application.
|
||||
mainClass = 'ch.zhaw.prog2.circularbuffer.CircBufferSimulation'
|
||||
}
|
||||
|
||||
// Required to allow System.in.read() when run with gradle.
|
||||
run {
|
||||
standardInput = System.in
|
||||
}
|
||||
|
||||
// Java plugin configuration
|
||||
java {
|
||||
// By default the Java version of the gradle process is used as source/target version.
|
||||
// This can be overridden, to ensure a specific version. Enable only if required.
|
||||
// sourceCompatibility = JavaVersion.VERSION_17 // ensure Java source code compatibility
|
||||
// targetCompatibility = JavaVersion.VERSION_17 // version of the created byte-code
|
||||
|
||||
// Java compiler specific options
|
||||
compileJava {
|
||||
// source files should be UTF-8 encoded
|
||||
options.encoding = 'UTF-8'
|
||||
// for more options see https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.CompileOptions.html
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package ch.zhaw.prog2.circularbuffer;
|
||||
|
||||
public interface Buffer<T> {
|
||||
boolean put(T element) throws InterruptedException;
|
||||
T get() throws InterruptedException;
|
||||
boolean empty();
|
||||
boolean full();
|
||||
int count();
|
||||
void printBufferSlots();
|
||||
void printBufferContent();
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package ch.zhaw.prog2.circularbuffer;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class CircBufferSimulation {
|
||||
public static void main(String[] args) {
|
||||
final int capacity = 15; // Number of buffer items
|
||||
final int prodCount = 1; // Number of producer threads
|
||||
final int consCount = 1; // Number of consumer threads
|
||||
final int maxProdTime = 500; // max. production time for one item
|
||||
final int maxConsTime = 500; // max. consumption time for one item
|
||||
|
||||
try {
|
||||
Buffer<String> buffer = new GuardedCircularBuffer<>(String.class, capacity);
|
||||
|
||||
// start consumers
|
||||
Consumer[] consumers = new Consumer[consCount];
|
||||
for (int i = 0; i < consCount; i++) {
|
||||
consumers[i] = new Consumer("Consumer_" + i, buffer, maxConsTime);
|
||||
consumers[i].start();
|
||||
}
|
||||
// start producers
|
||||
Producer[] producers = new Producer[prodCount];
|
||||
for (int i = 0; i < prodCount; i++) {
|
||||
producers[i] = new Producer("Producer_" + i, buffer, maxProdTime);
|
||||
producers[i].start();
|
||||
}
|
||||
|
||||
// print live buffer status
|
||||
ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(2);
|
||||
// print occupied slots of buffer every second
|
||||
scheduled.scheduleAtFixedRate(buffer::printBufferSlots, 1, 1, TimeUnit.SECONDS);
|
||||
// print content of buffer every 5 seconds
|
||||
scheduled.scheduleAtFixedRate(buffer::printBufferContent, 5,5, TimeUnit.SECONDS);
|
||||
|
||||
|
||||
System.out.println("Press Enter to terminate");
|
||||
System.in.read();
|
||||
|
||||
System.out.println("Shutting down ...");
|
||||
// shutdown producers
|
||||
for (Producer producer : producers) {
|
||||
producer.terminate();
|
||||
}
|
||||
// shutdown consumers
|
||||
for (Consumer consumer : consumers) {
|
||||
consumer.terminate();
|
||||
}
|
||||
// shutdown statistics
|
||||
scheduled.shutdown();
|
||||
System.out.println("Simulation ended.");
|
||||
|
||||
} catch (Exception logOrIgnore) {
|
||||
System.out.println(logOrIgnore.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static class Producer extends Thread {
|
||||
private volatile boolean running = true;
|
||||
private final Buffer<String> buffer;
|
||||
private final int maxProdTime;
|
||||
|
||||
public Producer(String name, Buffer<String> buffer, int prodTime) {
|
||||
super(name);
|
||||
this.buffer = buffer;
|
||||
maxProdTime = prodTime;
|
||||
}
|
||||
|
||||
public void terminate() {
|
||||
running = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
running = true;
|
||||
int number = 1;
|
||||
try {
|
||||
while (running) {
|
||||
buffer.put("#" + number++);
|
||||
sleep((int) (100 + Math.random() * maxProdTime));
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
System.err.println("Interrupted in " + getName() + ": " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class Consumer extends Thread {
|
||||
private volatile boolean running = true;
|
||||
private final Buffer<String> buffer;
|
||||
private final int maxConsTime;
|
||||
|
||||
public Consumer(String name, Buffer<String> buffer, int consTime) {
|
||||
super(name);
|
||||
this.buffer = buffer;
|
||||
maxConsTime = consTime;
|
||||
}
|
||||
|
||||
public void terminate() {
|
||||
running = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
running = true;
|
||||
try {
|
||||
while (running) {
|
||||
buffer.get();
|
||||
Thread.sleep(100 + (int) (Math.random() * maxConsTime));
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
System.err.println("Interrupted in " + getName() + ": " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package ch.zhaw.prog2.circularbuffer;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
|
||||
public class CircularBuffer<T> implements Buffer<T> {
|
||||
private static final char EMPTY = '-';
|
||||
private static final char FILLED = '*';
|
||||
private final StringBuffer printout;
|
||||
private final T[] items;
|
||||
private int count = 0;
|
||||
private int insertPosition = 0;
|
||||
private int outputPosition = 0;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public CircularBuffer(Class<T> clazz, int bufferSize) {
|
||||
printout = new StringBuffer();
|
||||
if (bufferSize <= 1)
|
||||
bufferSize = 1;
|
||||
this.items = (T[]) Array.newInstance(clazz, bufferSize);
|
||||
}
|
||||
|
||||
public boolean put(T item) {
|
||||
if (this.full())
|
||||
return false;
|
||||
items[insertPosition] = item;
|
||||
insertPosition = (insertPosition + 1) % items.length;
|
||||
count++;
|
||||
return true;
|
||||
}
|
||||
|
||||
public T get() {
|
||||
if (empty())
|
||||
return null;
|
||||
T item = items[outputPosition];
|
||||
outputPosition = (outputPosition + 1) % items.length;
|
||||
count--;
|
||||
return item;
|
||||
}
|
||||
|
||||
public boolean empty() {
|
||||
return count == 0;
|
||||
}
|
||||
|
||||
public boolean full() {
|
||||
return count >= items.length;
|
||||
}
|
||||
|
||||
public int count() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public void printBufferSlots() {
|
||||
printout.delete(0, printout.length());
|
||||
|
||||
int i = 0;
|
||||
|
||||
// from where to where is the buffer filled?
|
||||
printout.append("filled where: ||");
|
||||
|
||||
if (full()) {
|
||||
for (i = 0; i < items.length; i++)
|
||||
printout.append(FILLED);
|
||||
} else {
|
||||
char c1;
|
||||
char c2;
|
||||
if (insertPosition < outputPosition) { // if iPos < oPos, the buffer
|
||||
// starts with
|
||||
// filled slots at pos. 0; otherwise it
|
||||
// starts with empty slots
|
||||
c1 = FILLED; // * -> filled slot
|
||||
c2 = EMPTY; // - -> empty slot
|
||||
} else {
|
||||
c1 = EMPTY;
|
||||
c2 = FILLED;
|
||||
}
|
||||
for (i = 0; i < outputPosition && i < insertPosition; i++)
|
||||
printout.append(c1);
|
||||
for (; i < outputPosition || i < insertPosition; i++)
|
||||
printout.append(c2);
|
||||
for (; i < items.length; i++)
|
||||
printout.append(c1);
|
||||
}
|
||||
printout.append("|| how full: || ");
|
||||
|
||||
// how full is the buffer generally?
|
||||
for (i = 0; i < count; i++)
|
||||
printout.append(FILLED);
|
||||
for (; i < items.length; i++)
|
||||
printout.append(EMPTY);
|
||||
printout.append("||");
|
||||
System.out.println(printout);
|
||||
}
|
||||
|
||||
public void printBufferContent() {
|
||||
System.out.println("Anzahl Elemente im Puffer: " + count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
int index = (outputPosition + i) % items.length;
|
||||
System.out.println("index: " + index + " wert: " + items[index]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package ch.zhaw.prog2.circularbuffer;
|
||||
|
||||
public class GuardedCircularBuffer<T> implements Buffer<T> {
|
||||
private CircularBuffer<T> buffer;
|
||||
|
||||
public GuardedCircularBuffer(Class<T> clazz, int bufferSize) {
|
||||
buffer = new CircularBuffer<>(clazz, bufferSize);
|
||||
}
|
||||
|
||||
public synchronized boolean put(T item) throws InterruptedException {
|
||||
while (buffer.full()) {
|
||||
wait();
|
||||
}
|
||||
boolean retVal = buffer.put(item);
|
||||
notifyAll();
|
||||
return retVal;
|
||||
|
||||
}
|
||||
|
||||
public synchronized T get() throws InterruptedException {
|
||||
while (buffer.empty()) {
|
||||
wait();
|
||||
}
|
||||
T item = buffer.get();
|
||||
notifyAll();
|
||||
return item;
|
||||
|
||||
}
|
||||
|
||||
public synchronized void printBufferSlots() {
|
||||
buffer.printBufferSlots();
|
||||
}
|
||||
|
||||
public synchronized void printBufferContent() {
|
||||
buffer.printBufferContent();
|
||||
}
|
||||
|
||||
public synchronized boolean empty() {
|
||||
return buffer.empty();
|
||||
}
|
||||
|
||||
public synchronized boolean full() {
|
||||
return buffer.full();
|
||||
}
|
||||
|
||||
public synchronized int count() {
|
||||
return buffer.count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
package ch.zhaw.prog2.circularbuffer;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class GuardedCircularBufferTest {
|
||||
private static final int BUFFER_SIZE = 5;
|
||||
private static final String DEFAULT_ITEM = "Item";
|
||||
private GuardedCircularBuffer<String> buffer;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
buffer = new GuardedCircularBuffer<>(String.class, BUFFER_SIZE);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
buffer = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEmpty() {
|
||||
assertTrue(buffer.empty(), "Must return true if buffer is empty");
|
||||
assertDoesNotThrow(() -> { buffer.put("Some content"); }, "Must not throw an exception");
|
||||
assertFalse(buffer.empty(), "Must return false if buffer is not empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFull() {
|
||||
assertFalse(buffer.full(), "Must return false if buffer is empty");
|
||||
for(int num=0; num < BUFFER_SIZE; num++) {
|
||||
String item = DEFAULT_ITEM + " " + num;
|
||||
assertDoesNotThrow(() -> { buffer.put(item); }, "Must not throw an exception");
|
||||
}
|
||||
assertTrue(buffer.full(), "Must return true if buffer is full");
|
||||
assertDoesNotThrow(() -> { buffer.get(); }, "Must not throw an exception");
|
||||
assertFalse(buffer.full(), "Must return false if buffer is not full");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCount() {
|
||||
assertEquals(0, buffer.count(), "Initial should be 0");
|
||||
for(int num=1; num <= BUFFER_SIZE; num++) {
|
||||
String item = DEFAULT_ITEM + " " + num;
|
||||
assertDoesNotThrow(() -> { buffer.put(item); }, "Must not throw an exception");
|
||||
assertEquals(num, buffer.count());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testSinglePutGet() {
|
||||
assertTrue(buffer.empty(), "Make sure buffer is empty");
|
||||
assertDoesNotThrow(() -> { buffer.put(DEFAULT_ITEM); }, "Must not throw an exception");
|
||||
assertEquals(1, buffer.count());
|
||||
AtomicReference<String> returnItem = new AtomicReference<>();
|
||||
assertDoesNotThrow(() -> { returnItem.set(buffer.get()); }, "Must not throw an exception");
|
||||
assertEquals(DEFAULT_ITEM, returnItem.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMultiplePutGet() {
|
||||
assertTrue(buffer.empty(), "Make sure buffer is empty");
|
||||
// write items
|
||||
for (int num = 0; num < BUFFER_SIZE; num++) {
|
||||
String item = DEFAULT_ITEM + " " + num;
|
||||
assertDoesNotThrow(() -> {
|
||||
buffer.put(item);
|
||||
}, "Must not throw an exception");
|
||||
}
|
||||
// read items in same order
|
||||
for (int num = 0; num < BUFFER_SIZE; num++) {
|
||||
AtomicReference<String> returnItem = new AtomicReference<>();
|
||||
assertDoesNotThrow(() -> { returnItem.set(buffer.get()); }, "Must not throw an exception");
|
||||
assertEquals(DEFAULT_ITEM + " " + num, returnItem.get());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
void testBlockProduce() {
|
||||
assertTrue(buffer.empty(), "Make sure buffer is empty");
|
||||
ExecutorService executorService = Executors.newSingleThreadExecutor();
|
||||
try {
|
||||
executorService.execute(new Producer<String>(buffer, BUFFER_SIZE+1, DEFAULT_ITEM));
|
||||
TimeUnit.MILLISECONDS.sleep(100);
|
||||
assertTrue(buffer.full(), "Buffer should be full");
|
||||
executorService.shutdown();
|
||||
assertFalse(executorService.awaitTermination(3, TimeUnit.SECONDS), "Executor should be blocking");
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
fail("Interrupted executor", e);
|
||||
} finally {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBlockConsume() {
|
||||
assertTrue(buffer.empty(), "Make sure buffer is empty");
|
||||
ExecutorService executorService = Executors.newSingleThreadExecutor();
|
||||
try {
|
||||
executorService.execute(new Consumer<String>(buffer, 2));
|
||||
TimeUnit.MILLISECONDS.sleep(100);
|
||||
assertTrue(buffer.empty(), "Buffer should be empty");
|
||||
executorService.shutdown();
|
||||
assertFalse(executorService.awaitTermination(3, TimeUnit.SECONDS), "Executor should be blocking");
|
||||
} catch (InterruptedException e) {
|
||||
fail("Interrupted executor", e);
|
||||
} finally {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProduceThenConsume() {
|
||||
assertTrue(buffer.empty(), "Make sure buffer is empty");
|
||||
try {
|
||||
ExecutorService executorService = Executors.newSingleThreadExecutor();
|
||||
executorService.execute(new Producer<String>(buffer, BUFFER_SIZE, DEFAULT_ITEM));
|
||||
TimeUnit.MILLISECONDS.sleep(100);
|
||||
assertEquals(BUFFER_SIZE, buffer.count(), "Buffer must contain " + BUFFER_SIZE + " items");
|
||||
Consumer<String> consumer = new Consumer<>(buffer, BUFFER_SIZE);
|
||||
executorService.execute(consumer);
|
||||
TimeUnit.MILLISECONDS.sleep(100);
|
||||
assertEquals(0, buffer.count(), "Buffer must contain 0 items");
|
||||
Object[] expected = Stream.generate(() -> DEFAULT_ITEM).limit(BUFFER_SIZE).toArray();
|
||||
assertArrayEquals(expected, consumer.getItems().toArray());
|
||||
executorService.shutdown();
|
||||
assertTrue(executorService.awaitTermination(3, TimeUnit.SECONDS), "Timeout shutting down Executor");
|
||||
} catch (InterruptedException e) {
|
||||
fail("Interrupted executor", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProduceAndConsume() {
|
||||
assertTrue(buffer.empty(), "Make sure buffer is empty");
|
||||
ExecutorService executorService = Executors.newFixedThreadPool(2);
|
||||
try {
|
||||
executorService.execute(new Producer<String>(buffer, 10*BUFFER_SIZE, "Item"));
|
||||
TimeUnit.SECONDS.sleep(1);
|
||||
Consumer<String> consumer = new Consumer<>(buffer, 10*BUFFER_SIZE);
|
||||
executorService.execute(consumer);
|
||||
TimeUnit.SECONDS.sleep(1);
|
||||
List<String> receivedItems = consumer.getItems();
|
||||
assertEquals(10*BUFFER_SIZE, receivedItems.size());
|
||||
Object[] expected = Stream.generate(() -> DEFAULT_ITEM).limit(10*BUFFER_SIZE).toArray();
|
||||
assertArrayEquals(expected, consumer.getItems().toArray());
|
||||
executorService.shutdown();
|
||||
assertTrue(executorService.awaitTermination(3, TimeUnit.SECONDS), "Timeout shutting down Executor");
|
||||
} catch (InterruptedException e) {
|
||||
fail("Interrupted executor", e);
|
||||
} finally {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private class Producer<T> implements Runnable {
|
||||
private GuardedCircularBuffer<T> buffer;
|
||||
private int numItems;
|
||||
private T item;
|
||||
|
||||
public Producer(GuardedCircularBuffer<T> buffer, int numItems, T item) {
|
||||
this.buffer = buffer;
|
||||
this.numItems = numItems;
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
for (int num = 0; num < numItems; num++) {
|
||||
try {
|
||||
buffer.put(item);
|
||||
} catch (InterruptedException e) {
|
||||
System.err.println("Interrupted Producer at " + num + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Consumer<T> implements Runnable {
|
||||
private GuardedCircularBuffer<T> buffer;
|
||||
private int numItems;
|
||||
private List<T> items;
|
||||
|
||||
public Consumer(GuardedCircularBuffer<T> buffer, int numItems) {
|
||||
this.buffer = buffer;
|
||||
this.numItems = numItems;
|
||||
this.items = new ArrayList<>();
|
||||
}
|
||||
|
||||
public List<T> getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
for (int num = 0; num < numItems; num++) {
|
||||
try {
|
||||
this.items.add(buffer.get());
|
||||
} catch (InterruptedException e) {
|
||||
System.err.println("Interrupted Consumer at " + num + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Gradle build configuration for specific lab module / exercise
|
||||
* Default declarations can be found in the lab main build configuration (../../gradle.build)
|
||||
* Declarations in this file extend or override the default values.
|
||||
*/
|
||||
// the Java plugin is added by default in the main lab configuration
|
||||
plugins {
|
||||
// Apply the application plugin to add support for building a CLI application.
|
||||
id 'application'
|
||||
}
|
||||
|
||||
description = 'Lab04 TrafficLight Solution'
|
||||
|
||||
dependencies {
|
||||
|
||||
}
|
||||
|
||||
// Configuration for Application plugin
|
||||
application {
|
||||
// Define the main class for the application.
|
||||
mainClass = 'ch.zhaw.prog2.trafficlight.TrafficLightOperation'
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package ch.zhaw.prog2.trafficlight;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
class Car extends Thread {
|
||||
private final TrafficLight[] trafficLights;
|
||||
private int pos;
|
||||
|
||||
public Car(String name, TrafficLight[] trafficLights) {
|
||||
super(name);
|
||||
this.trafficLights = trafficLights;
|
||||
pos = 0; // start at first light
|
||||
start();
|
||||
}
|
||||
|
||||
public synchronized int position() {
|
||||
return pos;
|
||||
}
|
||||
|
||||
private void gotoNextLight() {
|
||||
pos = ++pos % trafficLights.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Random random = new Random();
|
||||
while (true) {
|
||||
try {
|
||||
sleep(random.nextInt(500));
|
||||
} catch (InterruptedException e) {
|
||||
System.out.println(e.getMessage());
|
||||
}
|
||||
trafficLights[pos].passby();
|
||||
gotoNextLight();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package ch.zhaw.prog2.trafficlight;
|
||||
|
||||
class TrafficLight {
|
||||
private boolean red;
|
||||
|
||||
public TrafficLight() {
|
||||
red = true;
|
||||
}
|
||||
|
||||
public synchronized void passby() {
|
||||
while (red) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException logOrIgnore) {
|
||||
System.out.println(logOrIgnore.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void switchToRed() {
|
||||
red = true;
|
||||
}
|
||||
|
||||
public synchronized void switchToGreen() {
|
||||
red = false;
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package ch.zhaw.prog2.trafficlight;
|
||||
|
||||
public class TrafficLightOperation {
|
||||
private static volatile boolean running = true;
|
||||
|
||||
public static void terminate () {
|
||||
running = false;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
TrafficLight[] trafficLights = new TrafficLight[7];
|
||||
Car[] cars = new Car[20];
|
||||
for (int i = 0; i < trafficLights.length; i++)
|
||||
trafficLights[i] = new TrafficLight();
|
||||
for (int i = 0; i < cars.length; i++) {
|
||||
cars[i] = new Car("Car " + i, trafficLights);
|
||||
}
|
||||
|
||||
// Simulation
|
||||
while (running) {
|
||||
for (int greenIndex = 0; greenIndex < trafficLights.length; greenIndex = greenIndex + 2) {
|
||||
// Display state of simulation
|
||||
System.out.println("=====================================================");
|
||||
for (int j = 0; j < trafficLights.length; j++) {
|
||||
String lightState;
|
||||
if (j == greenIndex || j == greenIndex + 1)
|
||||
lightState = "✅";
|
||||
else
|
||||
lightState = "🛑";
|
||||
System.out.print(lightState + " at Light " + j + ":");
|
||||
for (int carNumber = 0; carNumber < cars.length; carNumber++) {
|
||||
if (cars[carNumber].position() == j)
|
||||
System.out.print(" " + carNumber);
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
System.out.println("=====================================================");
|
||||
try {
|
||||
Thread.sleep(3000);
|
||||
} catch (InterruptedException logOrIgnore) {
|
||||
System.out.println(logOrIgnore.getMessage());
|
||||
}
|
||||
trafficLights[greenIndex].switchToGreen();
|
||||
if (greenIndex + 1 < trafficLights.length) {
|
||||
trafficLights[greenIndex + 1].switchToGreen();
|
||||
}
|
||||
|
||||
// green period
|
||||
try {
|
||||
Thread.sleep((int) (Math.random() * 500));
|
||||
} catch (InterruptedException logOrIgnore) {
|
||||
System.out.println(logOrIgnore.getMessage());
|
||||
}
|
||||
trafficLights[greenIndex].switchToRed();
|
||||
if (greenIndex + 1 < trafficLights.length)
|
||||
trafficLights[greenIndex + 1].switchToRed();
|
||||
|
||||
// red period
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException logOrIgnore) {
|
||||
System.out.println(logOrIgnore.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 168 KiB |
|
After Width: | Height: | Size: 1006 KiB |
|
After Width: | Height: | Size: 270 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 48 KiB |
@@ -0,0 +1,413 @@
|
||||
:source-highlighter: coderay
|
||||
:icons: font
|
||||
:experimental:
|
||||
:!sectnums:
|
||||
:imagesdir: ./images/
|
||||
:handout: ./
|
||||
|
||||
:logo: IT.PROG2 -
|
||||
ifdef::backend-html5[]
|
||||
:logo: image:PROG2-300x300.png[IT.PROG2,100,100,role=right,fit=none,position=top right]
|
||||
endif::[]
|
||||
ifdef::backend-pdf[]
|
||||
:logo:
|
||||
endif::[]
|
||||
ifdef::env-github[]
|
||||
:tip-caption: :bulb:
|
||||
:note-caption: :information_source:
|
||||
:important-caption: :heavy_exclamation_mark:
|
||||
:caution-caption: :fire:
|
||||
:warning-caption: :warning:
|
||||
endif::[]
|
||||
|
||||
= {logo} Lösungen zu den Übungen Concurrency – Cooperation
|
||||
|
||||
:sectnums:
|
||||
:sectnumlevels: 2
|
||||
// Beginn des Aufgabenblocks
|
||||
|
||||
== Concurrency 3 -- Thread Synchronisation
|
||||
|
||||
=== Konto-Übertrag [PU]
|
||||
|
||||
[loweralpha]
|
||||
. Was stellen Sie fest, wenn Sie die Simulation laufen lassen?
|
||||
Erklären Sie wie die Abweichungen zustande kommen.
|
||||
+
|
||||
****
|
||||
Bei den Transaktionen passieren sog. _lost updates_. Eine Transaktion ist nicht atomar und besteht aus mehreren Schritten (Wert aus Speicher lesen, verändern, Wert in Speicher schreiben).
|
||||
Durch die gleichzeitige Operation auf den Konten aus mehreren Threads können einzelne dieser Schritte ignoriert werden und verloren gehen.
|
||||
Beide Threads Lesen den gleichen Wert, aktualisieren diesen gleichzeitig, schreiben das neue Resultat (der letzte gewinnt).
|
||||
Am Schluss ist die Geldsumme über alle drei Konten oft grösser oder kleiner als am Anfang.
|
||||
****
|
||||
|
||||
. Im Unterricht haben Sie gelernt, dass kritische Bereiche Ihres Codes durch Mutual-Exclusion geschützt werden sollen.
|
||||
Wie macht man das in Java?
|
||||
+
|
||||
Versuchen Sie mittels von Mutual-Exclusion sicherzustellen, dass keine Abweichungen entstehen.
|
||||
+
|
||||
** Reicht es die kritischen Bereiche in Account zu sichern?
|
||||
** Welches sind die kritischen Bereiche?
|
||||
+
|
||||
****
|
||||
Siehe Klasse: `Account`
|
||||
|
||||
Monitor Objekte müssen so gewählt werden, dass sie die 'geteilten' Ressourcen schützen. Oft ist deshalb
|
||||
die geteilte Ressource selber das Monitor Objekt. +
|
||||
In diesem Fall wären es die `Account`-Objekte auf welche die von verschiedenen Threads zugegriffen wird. +
|
||||
Den Thread selber als Monitor zu verwenden macht wenig Sinn, da dann jeder Thread seinen eigenen Monitor besitzen würde.
|
||||
|
||||
[NOTE]
|
||||
Der Monitor von Thread-Objekten wird intern für `Thread.join()` verwendet. Beim Beenden eines Threads werden mit `notifyAll()` alle in einem `join()` wartenden Threads informiert. Deshalb sollte auf Thread-Instanzen niemals `wait()`, `notify()` oder `notifyAll()` verwendet werden. Siehe https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/lang/Thread.html#join(long,int)[JavaDoc von Thread.join()].
|
||||
|
||||
Kritische Bereiche sind diejenigen, in welchen auf eine geteilte Variable zugegriffen wird.
|
||||
Das kann sowohl schreibend, wie auch lesend erfolgen. Auch lesende Zugriffe sollten geschützt werden, da sonst nicht sichergestellt werden kann, dass der Wert während des Lesens nicht von einem anderen Thread verändert wird. +
|
||||
In `Account` sollte deshalb sowohl die Methode `setBalance(int amount)` wie auch `getBalance()` als `synchronized` deklariert werden.
|
||||
****
|
||||
+
|
||||
Untersuchen Sie mehrere Varianten von Locks (Lock auf Methode oder Block,
|
||||
Lock auf Instanz oder Klasse).
|
||||
+
|
||||
Ihre Implementierung muss noch nebenläufige Transaktionen erlauben, d.h. wenn
|
||||
Sie zu stark synchronisieren, werden alle Transaktionen in Serie ausgeführt und
|
||||
Threads ergeben keinen Sinn mehr.
|
||||
+
|
||||
Stellen Sie für sich folgende Fragen:
|
||||
+
|
||||
** Welches ist das Monitor-Objekt?
|
||||
** Braucht es eventuell das Lock von mehr als einem Monitor, damit eine Transaktion ungestört ablaufen kann?
|
||||
+
|
||||
****
|
||||
Siehe Klasse: `AccountTransferTask`
|
||||
|
||||
Auch in `AccountTransferTask` ist immer noch `Account` das Monitor-Objekt, da dort die Daten effektiv verändert werden. Da in `transfer()` jedoch gleichzeitig mit zwei Konten gearbeitet wird, sollte sichergestellt werden, dass nur ein Thread auf exakt die beiden Konten Zugriff hat, damit die Überprüfung und Abbuchung von einem Konto und der Übertrag auf das Andere als eine atomare Funktion erfolgt.
|
||||
Das heißt wir erhalten einen neuen _kritischen Bereich_ den wir mit synchronized schützen müssen.
|
||||
Diesmal in dem der Monitor von `fromAccount` und `toAccount` im Voraus schon akquiriert und somit sicherstellt wird, dass `transferAmmount` von beiden Konten nicht mehr blockieren kann. +
|
||||
In Java kann das mit einer geschachtelten Synchronisation erfolgen:
|
||||
|
||||
[source, Java]
|
||||
----
|
||||
synchronized(fromAccount) {
|
||||
synchronized(toAccount) {
|
||||
if (fromAccount.getBalance() >= amount) {
|
||||
fromAccount.transferAmount(-amount);
|
||||
toAccount.transferAmount(amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
----
|
||||
|
||||
Warum kann man nicht einfach die Methode `transfer()` synchronized deklarieren? +
|
||||
Damit würde die `AccountTransferTask`-Instanz selber als drittes Monitor-Objekt verwendet.
|
||||
Damit werden nur die Threads synchronisiert, welche das gleiche Task-Objekt verwenden.
|
||||
Da drei Task-Objekte erstellt werden, könnten zum Beispiel je ein Thread für `task2` und `task3` gleichzeitig auf `account2` zugreifen.
|
||||
Würde für jede Transaktion sogar eine eigene `AccountTransferTask`-Instanz erzeugt, hätte das synchronized auf `transfer()` überhaupt keinen Effekt mehr, da dann jeder Thread seinen eigenen Monitor besitzt.
|
||||
****
|
||||
|
||||
. Wenn Sie es geschafft haben die Transaktion thread-safe zu implementieren,
|
||||
ersetzen Sie in `AccountTransferSimulation` die folgende Zeile: +
|
||||
`AccountTransferTask task1 = new AccountTransferTask(account**3**, account**1**, 2);` +
|
||||
durch +
|
||||
`AccountTransferTask task1 = new AccountTransferTask(account**1**, account**3**, 2);` +
|
||||
und starten Sie das Programm noch einmal.
|
||||
Was stellen Sie fest? (evtl. müssen Sie es mehrfach versuchen, damit der Effekt auftritt). +
|
||||
Was könnte die Ursache sein und wie können Sie es beheben? +
|
||||
[NOTE]
|
||||
Falls Sie die Frage noch nicht beantworten können, kommen sie nach der Vorlesung "Concurrency 4" nochmals auf diese Aufgabe zurück und versuchen Sie sie dann zu lösen.
|
||||
+
|
||||
****
|
||||
Durch die Umstellung kann es zu einem zirkulären Deadlock kommen, da sich die Threads gegenseitig blockieren (jeder hat ein Konto bereits gelockt und wartet auf ein anderes).
|
||||
|
||||
Die einfachste Lösung ist es die Konten immer in einer übergeordneten festen Reihenfolge zu akquirieren.
|
||||
In diesem Fall zum Beispiel immer zuerst die tiefere Kontonummer.
|
||||
Dadurch kann es keine zirkulären Abhängigkeiten mehr geben.
|
||||
|
||||
Lösung: `AccountTransferTask.transferDLfree()`
|
||||
****
|
||||
|
||||
=== Traffic Light [PU]
|
||||
|
||||
In dieser Aufgabe sollen Sie die Funktionsweise einer Ampel und deren Nutzung nachahmen.
|
||||
Benutzen Sie hierzu die Vorgabe im Modul `TrafficLight`.
|
||||
|
||||
[loweralpha]
|
||||
. Ergänzen Sie zunächst in der Klasse `TrafficLight` drei Methoden:
|
||||
* Eine Methode zum Setzen der Ampel auf "rot".
|
||||
* Eine Methode zum Setzen der Ampel auf "grün".
|
||||
* Eine Methode mit dem Namen `passby()`. Diese Methode soll das Vorbeifahren
|
||||
eines Fahrzeugs an dieser Ampel nachbilden: Ist die Ampel rot, so wird der
|
||||
aufrufende Thread angehalten, und zwar so lange, bis die Ampel grün wird.
|
||||
Ist die Ampel dagegen grün, so kann der Thread sofort aus der Methode zurückkehren,
|
||||
ohne den Zustand der Ampel zu verändern. Verwenden Sie `wait`, `notify` und
|
||||
`notifyAll` nur an den unbedingt nötigen Stellen!
|
||||
+
|
||||
[NOTE]
|
||||
Die Zwischenphase „gelb“ spielt keine Rolle – Sie können diesem Zustand ignorieren!
|
||||
+
|
||||
****
|
||||
Lösung siehe: `ch.zhaw.prog2.trafficlight.TrafficLight`
|
||||
****
|
||||
|
||||
. Erweitern Sie nun die Klasse `Car` (abgeleitet von `Thread`). +
|
||||
Im Konstruktor wird eine Referenz auf ein Feld von Ampeln übergeben.
|
||||
Diese Referenz wird in einem entsprechenden Attribut der Klasse `Car` gespeichert.
|
||||
In der `run`-Methode werden alle Ampeln dieses Feldes passiert, und zwar in einer Endlosschleife (d.h. nach dem Passieren der letzten Ampel des Feldes wird wieder die erste Ampel im Feld passiert). +
|
||||
Natürlich darf das Auto erst dann eine Ampel passieren, wenn diese auf grün ist! +
|
||||
Für die Simulation der Zeitspanne für das Passieren des Signals sollten Sie folgende Anweisung verwenden: `sleep\((int)(Math.random() * 500));`
|
||||
+
|
||||
****
|
||||
Lösung Siehe: `ch.zhaw.prog2.trafficlight.Car`
|
||||
****
|
||||
|
||||
Beantworten Sie entweder (c) oder (d) (nicht beide):
|
||||
|
||||
[loweralpha, start=3]
|
||||
. Falls Sie bei der Implementierung der Klasse TrafficLight die Methode
|
||||
`notifyAll()` benutzt haben: +
|
||||
Hätten Sie statt `notifyAll` auch die Methode `notify` verwenden können, oder haben Sie `notifyAll()` unbedingt gebraucht?
|
||||
Begründen Sie Ihre Antwort!
|
||||
+
|
||||
****
|
||||
Mit `notifyAll()` erhalten alle Autos die Gelegenheit die Ampel zu überqueren.
|
||||
Natürlich betreten sie den Monitor (`TrafficLight`) immer noch einzeln und können, falls die Zeitspanne nicht, reicht trotzdem stehen bleiben. Dann erhalten Sie bei der nächsten "Grünen Welle" erneut eine Notifikation und können es nochmals probieren.
|
||||
|
||||
Bei `notify()` würde immer nur ein Auto an der Ampel losfahren. +
|
||||
Man könnte das beheben, indem jedes Auto vor dem Losfahren noch ein weiteres `notify()` erzeugt.
|
||||
Das ist aber eher ein work-around und nicht sehr schön.
|
||||
****
|
||||
|
||||
. Falls Sie bei der Implementierung der Klasse Ampel die Methode `notify()` benutzt
|
||||
haben: +
|
||||
Begründen Sie, warum `notifyAll()` nicht unbedingt benötigt wird!
|
||||
+
|
||||
****
|
||||
Siehe oben.
|
||||
****
|
||||
|
||||
. Testen Sie das Programm `TrafficLightOperation.java`.
|
||||
Die vorgegebene Klasse implementiert eine primitive Simulation von Autos, welche die Ampeln passieren.
|
||||
Studieren Sie den Code dieser Klasse und überprüfen Sie, ob die erzeugte Ausgabe sinnvoll ist.
|
||||
|
||||
|
||||
=== Producer-Consumer Problem [PU]
|
||||
|
||||
In dieser Aufgabe wird ein Producer-Consumer Beispiel mittels einer Queue umgesetzt.
|
||||
|
||||
Dazu wird eine Implementation mittels eines link:https://en.wikipedia.org/wiki/Circular_buffer[Digitalen Ringspeichers] umgesetzt.
|
||||
|
||||
.Circular Buffer [Wikipedia]
|
||||
[link = https://en.wikipedia.org/wiki/Circular_buffer]
|
||||
image::Circular_Buffer_Animation.gif[pdfwidth=75%, width=600px]
|
||||
|
||||
Hierzu sind zwei Klassen (`CircularBuffer.java`, `Buffer.java`) vorgegeben, mit folgendem Design:
|
||||
|
||||
.Circular Buffer Design
|
||||
image::CircularBuffer.png[pdfwidth=75%, width=600px]
|
||||
|
||||
|
||||
==== Analyse der abgegebenen CircularBuffer Umsetzung.
|
||||
|
||||
Mit dem Testprogramm `CircBufferSimulation` kann die Funktion der `CircularBuffer` Implementierung analysiert werden.
|
||||
Der mitgelieferte `Producer`-Thread generiert kontinuierlich Daten (in unserem Fall aufsteigende Nummern) und füllt diese mit `buffer.put(...)` in den Buffer.
|
||||
Der `Consumer`-Thread liest die Daten kontinuierlich mit `buffer.get()` aus dem Buffer aus.
|
||||
Beide Threads benötigen eine gewisse Zeit zum Produzieren bzw. Konsumieren der Daten.
|
||||
Diese kann über die Variablen `maxProduceTime` bzw. `maxConsumeTime` beeinflusst werden.
|
||||
Es können zudem mehrere Producer- bzw. Consumer-Threads erzeugt werden.
|
||||
|
||||
[loweralpha]
|
||||
. Starten Sie `CircularBufferSimulation` und analysieren Sie die Ausgabe.
|
||||
Der Status des Buffers (belegte Positionen und Füllstand) wird sekündlich ausgegeben.
|
||||
Alle fünf Sekunden wird zudem der aktuelle Inhalt des Buffers ausgegeben. +
|
||||
** Wie ist das Verhalten des `CircularBuffer` bei den Standard-Testeinstellungen?
|
||||
+
|
||||
****
|
||||
`Producer` und `Consumer` arbeiten etwa im gleichen Rhythmus.
|
||||
Das heißt der Buffer ist immer leicht gefüllt, aber nie ganz voll oder leer.
|
||||
****
|
||||
|
||||
. Analysieren Sie die Standard-Einstellungen in `CircularBufferSimulation`.
|
||||
** Welche Varianten gibt es, die Extrempositionen (Buffer leer, bzw. Buffer voll) zu erzeugen?
|
||||
+
|
||||
****
|
||||
Damit der Buffer immer etwa gleich gefüllt ist und alles reibungslos funktioniert muss
|
||||
`prodCount * maxProducetime ≈ consCount * maxConsumeTime` sein.
|
||||
Das heißt es wird gleichviel produziert, wie konsumiert.
|
||||
|
||||
**Buffer leer** → es wird mehr konsumiert als produziert
|
||||
|
||||
* Mehr Zeit für Produktion: `maxProduceTime` > `maxConsumeTime` setzen.
|
||||
* Mehr Konsumenten als Produzenten: `prodCount` < `consCount`
|
||||
|
||||
**Buffer voll** → es wird mehr produziert als konsumiert
|
||||
|
||||
* Mehr Zeit für Konsumation: `maxProduceTime` < `maxConsumeTime` setzen.
|
||||
* Mehr Produzenten als Konsumenten: `prodCount` > `consCount`
|
||||
****
|
||||
|
||||
** Was ist das erwartete Verhalten bei vollem bzw. leerem Buffer? (bei Producer bzw. Consumer)
|
||||
+
|
||||
****
|
||||
**Buffer leer** → `Consumer` muss warten, bis wieder Daten vorhanden sind. +
|
||||
**Buffer voll** → `Producer` muss warten, bis wieder Platz für Daten vorhanden ist
|
||||
****
|
||||
|
||||
. Testen Sie was passiert, wenn der Buffer an die Kapazitätsgrenze kommt. Passen Sie dazu die Einstellungen in `CircularBufferSimulation` entsprechend an. +
|
||||
[TIP]
|
||||
Belassen sie die Anzahl Producer-Threads vorerst auf 1, damit der Inhalt des Buffers (Zahlenfolge) einfacher verifiziert werden kann.
|
||||
+
|
||||
** Was Stellen Sie fest? Was passiert wenn der Buffer voll ist? Warum?
|
||||
+
|
||||
****
|
||||
Damit es einfacher verfolgt werden kann, sollte nur `maxProduceTime` verkleinert bzw. `maxConsumeTime` vergrössert werden.
|
||||
|
||||
Sobald der Buffer voll ist, werden die neue produzierten Daten _ignoriert_; d.h. sozusagen weggeworfen.
|
||||
Das ist gut an den Lücken in der Zahlenfolge im Buffer zu erkennen.
|
||||
****
|
||||
|
||||
. Wiederholen Sie das Gleiche für einen leeren Buffer. Passen Sie die Einstellungen so an, dass der Buffer sicher leer wird, d.h. der `Consumer` keine Daten vorfindet.
|
||||
** Was stellen Sie fest, wenn der Buffer leer ist? Warum? +
|
||||
[TIP]
|
||||
Geben Sie gegebenenfalls die gelesenen Werte des Konsumenten-Threads aus.
|
||||
+
|
||||
****
|
||||
Hierzu muss die `maxProduceTime` vergrössert bzw. `maxConsumeTime` verringert werden.
|
||||
|
||||
Damit man den Effekt sehen kann, muss im `Consumer` der Inhalt des konsumierten Strings ausgegeben werden.
|
||||
Sie stellen fest, dass `null`-Werte geliefert werden, sobald der Buffer leer ist.
|
||||
Es werden also _Fake-Daten_ konsumiert bzw. weiterverarbeitet.
|
||||
****
|
||||
|
||||
==== Thread-Safe Circular Buffer
|
||||
In der vorangehenden Übung griffen mehrere Threads (`Producer` & `Consumer`) auf den gleichen Buffer zu.
|
||||
Die Klasse `CircularBuffer` ist aber nicht thread-safe.
|
||||
Deshalb soll jetzt eine Wrapper Klasse geschrieben werden, welche die CircularBuffer-Klasse "thread-safe" macht.
|
||||
Das führt zu folgendem Design:
|
||||
|
||||
.Guarded Circular Buffer Design
|
||||
image::GuardedCircularBuffer.png[pdfwidth=75%, width=600px]
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Beachten Sie, dass es sich hier um einen Wrapper (keine Vererbung) handelt. +
|
||||
Der `GuardedCircularBuffer` hält eine Referenz auf ein `CircularBuffer`-Objekt welches er im Hintergrund für die Speicherung verwendet. Das heißt, viele Methodenaufrufe werden einfach an das gekapselte Objekt weitergeleitet. Einzelne Methoden werden jedoch in ihrer Funktion erweitert. Man spricht auch von "Dekorieren" des Original-Objektes (siehe link:{decorator-pattern}[Decorator-Pattern]).
|
||||
====
|
||||
|
||||
:decorator-pattern: https://en.wikipedia.org/wiki/Decorator_pattern
|
||||
|
||||
[loweralpha, start=5]
|
||||
. Ergänzen Sie die vorhandene Klasse `GuardedCircularBuffer` sodass:
|
||||
** Die Methoden `empty`, `full`, `count` das korrekte Resultat liefern.
|
||||
** Aufrufe von `put` blockieren, solange der Buffer voll ist, d.h. bis mindestens wieder ein leeres Buffer-Element vorhanden ist.
|
||||
** Analog dazu Aufrufe von `get` blockieren, solange der Buffer leer ist, d.h, bis mindestens ein Element im Buffer vorhanden ist.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
Verwenden Sie den Java Monitor des `GuardedCircularBuffer`-Objektes!
|
||||
Wenn die Klasse fertig implementiert ist, soll sie in der `CircBufferSimulation` Klasse verwendet werden.
|
||||
====
|
||||
|
||||
****
|
||||
Siehe Lösung: `ch.zhaw.prog2.circularbuffer.GuardedCircularBuffer`
|
||||
|
||||
Als erstes müssen sicher die Methoden `get` und `put` als `synchronized` deklariert werden.
|
||||
Da jedoch auch die anderen Methoden auf den Status des Buffers zugreifen, müssen auch diese `synchronized` sein.
|
||||
|
||||
Bei `get` und `put` wird jeweils in einer `while`-Schleife der Zustand überprüft und falls nicht erfüllt (`get` → buffer leer, `put` → buffer voll) mit `wait()` gewartet.
|
||||
|
||||
Sobald ein Element hinzugefügt bzw. gelesen wurde, werden die wartenden `Consumer` oder `Producer` Threads mit `notify()/notifyAll()` benachrichtigt.
|
||||
|
||||
Die eigentlichen Operationen werden auf einem gekapselten `CircularBuffer`-Objekt ausgeführt, welches im Konstruktor erzeugt wird.
|
||||
****
|
||||
|
||||
Beantworten Sie entweder (i) oder (ii) (nicht beide):
|
||||
|
||||
[lowerroman]
|
||||
. Falls Sie bei der Implementierung der Klasse `GuardedCircularBuffer` die Methode `notifyAll()` benutzt haben:
|
||||
Hätten Sie statt `notifyAll()` auch die Methode `notify()` verwenden können oder haben Sie `notifyAll()` unbedingt
|
||||
benötigt? Begründen Sie Ihre Antwort!
|
||||
|
||||
. Falls Sie bei der Implementierung der Klasse `GuardedCircularBuffer` die Methode `notify()` benutzt haben:
|
||||
Begründen Sie, warum Sie `notifyAll()` nicht unbedingt benötigten!
|
||||
|
||||
****
|
||||
Da bei vernünftiger Dimensionierung des Buffers & der Anzahl Threads, sollten jeweils nur entweder Produzenten oder Konsumenten am Warten sein.
|
||||
Dann reicht beim Entfernen bzw. Hinzufügen eines Elementes ein `notify()`.
|
||||
Es wird dann einer der wartenden Threads aufgeweckt, der ein Element hinzufügen bzw. entfernen kann.
|
||||
Beim nächsten Hinzufügen bzw. Entfernen wird ja wieder eine neue Notifikation erzeugt.
|
||||
|
||||
Wenn der Buffer aber sehr klein und die Zahl der Produzenten / Konsumenten gross ist, könnte der Fall auftreten, dass sowohl Konsumenten, wie auch Produzenten am Warten sind.
|
||||
|
||||
Um sicherzugehen, dass sicher ein Produzent bzw. Konsument zum Zug kommt, wecken wir deshalb am besten alle wartenden Threads.
|
||||
|
||||
Das hat aber den Nachteil, dass alle ihre Bedingung überprüfen müssen, obwohl nur der erste passende Thread zum Zuge kommt, da ja nur ein Platz frei wurde, der gefüllte bzw. ein Element vorhanden ist, das abgeholt werden kann.
|
||||
****
|
||||
|
||||
|
||||
== Concurrency 4 -- Lock & Conditions, Deadlocks
|
||||
|
||||
=== Single-Lane Bridge [PU]
|
||||
|
||||
Die Brücke über einen Fluss ist so schmal, dass Fahrzeuge nicht kreuzen können.
|
||||
Sie soll jedoch von beiden Seiten überquert werden können.
|
||||
Es braucht somit eine Synchronisation, damit die Fahrzeuge nicht kollidieren.
|
||||
Um das Problem zu illustrieren wird eine fehlerhaft funktionierende Anwendung,
|
||||
in welcher keine Synchronisierung vorgenommen wird, zur Verfügung gestellt.
|
||||
Ihre Aufgabe ist es, die Synchronisation der Fahrzeuge einzubauen.
|
||||
|
||||
Die Anwendung finden Sie im link:{handout}[Praktikumsverzeichnis] im Modul `Bridge`.
|
||||
Gestartet wird sie indem die Klasse `Main` ausgeführt wird (z.B. mit `gradle run`).
|
||||
Das GUI sollte selbsterklärend sein.
|
||||
Mit den zwei Buttons können sie Autos links bzw. rechts hinzufügen. Sie werden feststellen, dass die Autos auf der Brücke kollidieren, bzw. illegalerweise durcheinander hindurchfahren.
|
||||
|
||||
.Single-Lane Bridge GUI
|
||||
image::bridge_overview.png[pdfwidth=75%, width=600px]
|
||||
|
||||
Um das Problem zu lösen, müssen Sie die den GUI Teil der Anwendung nicht verstehen.
|
||||
Sie müssen nur wissen, dass Fahrzeuge, die von links nach rechts fahren, die Methode `controller.enterLeft()` aufrufen bevor sie auf die Brücke fahren (um Erlaubnis fragen) und die Methode `controller.leaveRight()` aufrufen, sobald sie die Brücke verlassen.
|
||||
Fahrzeuge in die andere Richtung rufen entsprechend die Methoden `enterRight()` und `leaveLeft()` auf.
|
||||
Dabei ist `controller` eine Instanz der Klasse `TrafficController`, welche für die Synchronisation zuständig ist.
|
||||
In der mitgelieferten Klasse sind die vier Methoden nicht implementiert (Dummy-Methoden).
|
||||
|
||||
[loweralpha]
|
||||
. Erweitern sie `TrafficController` zu einer Monitorklasse, die sicherstellt, dass die Autos nicht mehr kollidieren.
|
||||
Verwenden Sie dazu den Lock und Conditions Mechanismus.
|
||||
[TIP]
|
||||
Verwenden Sie eine Statusvariable, um den Zustand der Brücke zu repräsentieren (e.g. `boolean bridgeOccupied`).
|
||||
+
|
||||
****
|
||||
Siehe Code: `ch.zhaw.prog2.bridge.TrafficControllerA`
|
||||
****
|
||||
|
||||
. Passen Sie die Klasse `TrafficController` so an, dass jeweils abwechslungsweise ein Fahrzeug von links und rechts die Brücke passieren kann.
|
||||
Unter Umständen wird ein Auto blockiert, wenn auf der anderen Seite keines mehr wartet.
|
||||
Verwenden Sie für die Lösung mehrere Condition Objekte.
|
||||
[NOTE]
|
||||
Um die Version aus a. zu behalten, können sie auch eine Kopie (z.B. `TrafficControllerB`) erzeugen und `Main` anpassen, damit eine Instanz dieser Klasse verwendet wird.
|
||||
+
|
||||
****
|
||||
Siehe Code: `ch.zhaw.prog2.bridge.TrafficControllerB`
|
||||
****
|
||||
|
||||
. Bauen Sie die Klasse `TrafficController` so um, dass jeweils alle wartenden Fahrzeuge aus einer Richtung passieren können.
|
||||
Erst wenn keines mehr wartet, kann die Gegenrichtung fahren.
|
||||
[TIP]
|
||||
Mit link:{ReentrantLock}[`ReentrentLock.hasWaiters(Condition c)`] können Sie
|
||||
abfragen ob Threads auf eine bestimmte Condition warten.
|
||||
+
|
||||
****
|
||||
Siehe Code: `ch.zhaw.prog2.bridge.TrafficControllerC`
|
||||
****
|
||||
|
||||
:ReentrantLock: https://docs.oracle.com/en/java/javase/16/docs/api/java.base/java/util/concurrent/locks/ReentrantLock.html#hasWaiters(java.util.concurrent.locks.Condition)
|
||||
|
||||
|
||||
=== The Dining Philosophers [PA]
|
||||
|
||||
****
|
||||
Die Lösungen zu den bewerteten Pflichtaufgaben erhalten Sie nach der Abgabe und Bewertung aller Klassen.
|
||||
****
|
||||
|
||||
// Ende des Aufgabenblocks
|
||||
:!sectnums:
|
||||
// == Aufräumarbeiten
|
||||