Initial commit
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
+11
@@ -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 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+102
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+49
@@ -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();
|
||||
}
|
||||
}
|
||||
+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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user