diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..4944549
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000..8fc3979
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ file://$PROJECT_DIR$/src/test/java/org/labs/FairnessTest.java
+ 29
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gradlew b/gradlew
old mode 100644
new mode 100755
diff --git a/src/main/java/org/labs/Main.java b/src/main/java/org/labs/Main.java
index 9917247..ba14c36 100644
--- a/src/main/java/org/labs/Main.java
+++ b/src/main/java/org/labs/Main.java
@@ -1,7 +1,28 @@
package org.labs;
public class Main {
- public static void main(String[] args) {
- System.out.println("Hello, World!");
+
+ public static void main(String[] args) throws InterruptedException {
+ startDinner(7, 2, 1000);
+ }
+
+ public static void startDinner(int progsCount, int waitersCount, int portions) throws InterruptedException {
+ SpoonManager spoonManager = new SpoonManager(progsCount);
+ Programmer[] programmers = new Programmer[progsCount];
+ WaiterService waiterService = new WaiterService(waitersCount, portions);
+ for (int i = 0; i < progsCount; i++) {
+ programmers[i] = new Programmer(i, spoonManager, waiterService);
+ programmers[i].start();
+ }
+
+ int total = 0;
+ for (int i = 0; i < progsCount; i++) {
+ programmers[i].join();
+ total += programmers[i].getEaten();
+ }
+
+ waiterService.shutdown();
+ System.out.println("TOTAL EATEN " + total);
}
-}
\ No newline at end of file
+}
+
diff --git a/src/main/java/org/labs/Programmer.java b/src/main/java/org/labs/Programmer.java
new file mode 100644
index 0000000..9bb07f6
--- /dev/null
+++ b/src/main/java/org/labs/Programmer.java
@@ -0,0 +1,80 @@
+package org.labs;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class Programmer extends Thread {
+
+ private final int id;
+ private final SpoonManager spoonManager;
+ private final WaiterService waiterService;
+
+ private boolean hasPortion;
+ private int eaten = 0;
+
+ public Programmer(int id, SpoonManager manager, WaiterService waiterService) {
+ this.id = id;
+ this.spoonManager = manager;
+ this.waiterService = waiterService;
+ this.hasPortion = false;
+ }
+
+ @Override
+ public void run() {
+ while (!Thread.currentThread().isInterrupted()) {
+ code();
+
+ hasPortion = requestFoodWithTimeout(eaten);
+ if (!hasPortion) {
+ break;
+ }
+
+ spoonManager.aquireSpoons(id);
+ try {
+ eat();
+ } finally {
+ spoonManager.releaseSpoons(id);
+ }
+ }
+ System.out.println("Programmer-" + id + " has eaten " + eaten + " portions");
+ }
+
+ public int getEaten() {
+ return eaten;
+ }
+
+ private boolean requestFoodWithTimeout(int eaten) {
+ try {
+ CompletableFuture getFoodFuture = waiterService.requestFood(eaten);
+ return getFoodFuture.get(5, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return false;
+ } catch (ExecutionException e) {
+ System.err.println("Waiter failed to bring new portion: " + e.getCause());
+ return false;
+ } catch (TimeoutException e) {
+ System.err.println("No response from waiter");
+ return false;
+ }
+ }
+
+ private void eat() {
+ try {
+ Thread.sleep(TimeUtil.randomTime(TimeUtil.MIN_WAIT_TIME, TimeUtil.MAX_WAIT_TIME));
+ eaten++;
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ private void code() {
+ try {
+ Thread.sleep(TimeUtil.randomTime(TimeUtil.MIN_WAIT_TIME, TimeUtil.MAX_WAIT_TIME));
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+}
diff --git a/src/main/java/org/labs/SpoonManager.java b/src/main/java/org/labs/SpoonManager.java
new file mode 100644
index 0000000..d40aaae
--- /dev/null
+++ b/src/main/java/org/labs/SpoonManager.java
@@ -0,0 +1,37 @@
+package org.labs;
+
+import java.util.concurrent.locks.ReentrantLock;
+
+public class SpoonManager {
+ private final int spoonCount;
+ private final ReentrantLock[] issued;
+
+ public SpoonManager(int spoonCount) {
+ this.spoonCount = spoonCount;
+ this.issued = new ReentrantLock[spoonCount];
+ for (int i = 0; i < spoonCount; i++) {
+ issued[i] = new ReentrantLock();
+ }
+ }
+
+ public void aquireSpoons(int programmerId) {
+ int leftSpoonIdx = leftSpoonIdx(programmerId);
+ int rightSpoonIdx = rightSpoonIdx(programmerId);
+
+ issued[Math.min(leftSpoonIdx, rightSpoonIdx)].lock();
+ issued[Math.max(leftSpoonIdx, rightSpoonIdx)].lock();
+ }
+
+ public void releaseSpoons(int programmerId) {
+ issued[leftSpoonIdx(programmerId)].unlock();
+ issued[rightSpoonIdx(programmerId)].unlock();
+ }
+
+ private int rightSpoonIdx(int programmerId) {
+ return programmerId;
+ }
+
+ private int leftSpoonIdx(int programmerId) {
+ return (programmerId + 1) % spoonCount;
+ }
+}
diff --git a/src/main/java/org/labs/TimeUtil.java b/src/main/java/org/labs/TimeUtil.java
new file mode 100644
index 0000000..34b2f08
--- /dev/null
+++ b/src/main/java/org/labs/TimeUtil.java
@@ -0,0 +1,14 @@
+package org.labs;
+
+public class TimeUtil {
+ public static final Integer MIN_WAIT_TIME = 1;
+ public static final Integer MAX_WAIT_TIME = 3;
+
+ public static long randomTime(int min, int max) {
+ return min + (long)(Math.random() * (max - min));
+ }
+
+ private TimeUtil() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/src/main/java/org/labs/Waiter.java b/src/main/java/org/labs/Waiter.java
new file mode 100644
index 0000000..1a3bb25
--- /dev/null
+++ b/src/main/java/org/labs/Waiter.java
@@ -0,0 +1,35 @@
+package org.labs;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class Waiter extends Thread {
+
+ private final int id;
+ private final BlockingQueue taskQueue;
+
+ public Waiter(int id, BlockingQueue taskQueue) {
+ this.id = id;
+ this.taskQueue = taskQueue;
+ }
+
+ @Override
+ public void run() {
+ while (!Thread.currentThread().isInterrupted()) {
+ try {
+ WaiterTask task = taskQueue.poll(1, TimeUnit.SECONDS);
+ if (task != null) {
+ task.run();
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ break;
+ }
+ }
+ System.out.printf("Waiter-%d is shutting down%n", id);
+ }
+
+ public void stopWorking() {
+ this.interrupt();
+ }
+}
diff --git a/src/main/java/org/labs/WaiterService.java b/src/main/java/org/labs/WaiterService.java
new file mode 100644
index 0000000..249ccf6
--- /dev/null
+++ b/src/main/java/org/labs/WaiterService.java
@@ -0,0 +1,51 @@
+package org.labs;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.PriorityBlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Сервис для обслуживания программистов
+ */
+public class WaiterService {
+ private final AtomicInteger portions;
+ private final PriorityBlockingQueue blockingQueue;
+ private final Waiter[] waiters;
+
+ public WaiterService(int waitersCount, int portionCount) {
+ blockingQueue = new PriorityBlockingQueue<>();
+ portions = new AtomicInteger(portionCount);
+ waiters = new Waiter[waitersCount];
+ for (int i = 0; i < waitersCount; i++) {
+ waiters[i] = new Waiter(i, blockingQueue);
+ }
+
+ startWaiters();
+ }
+
+ public CompletableFuture requestFood(int eatenBefore) throws InterruptedException {
+ CompletableFuture future = new CompletableFuture<>();
+ blockingQueue.offer(new WaiterTask(eatenBefore, portions, future));
+ return future;
+ }
+
+ private void startWaiters() {
+ for (Waiter waiter : waiters) {
+ waiter.start();
+ }
+ }
+
+ public void shutdown() {
+ for (Waiter waiter : waiters) {
+ waiter.stopWorking();
+ }
+
+ for (Waiter waiter : waiters) {
+ try {
+ waiter.join(3000);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/labs/WaiterTask.java b/src/main/java/org/labs/WaiterTask.java
new file mode 100644
index 0000000..59cb389
--- /dev/null
+++ b/src/main/java/org/labs/WaiterTask.java
@@ -0,0 +1,40 @@
+package org.labs;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class WaiterTask implements Runnable, Comparable {
+ private final int eatenBefore;
+ private final AtomicInteger portions;
+ private final CompletableFuture resultFuture;
+
+ public WaiterTask(int eatenBefore, AtomicInteger portions, CompletableFuture resultFuture) {
+ this.eatenBefore = eatenBefore;
+ this.portions = portions;
+ this.resultFuture = resultFuture;
+ }
+
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(TimeUtil.randomTime(TimeUtil.MIN_WAIT_TIME, TimeUtil.MAX_WAIT_TIME));
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ int remainingPortions = portions.get();
+ if (remainingPortions % 5000 == 0) {
+ System.out.println("Portions left - " + remainingPortions);
+ }
+ boolean result = portions.getAndDecrement() > 0;
+ resultFuture.complete(result);
+ }
+
+ @Override
+ public int compareTo(WaiterTask o) {
+ return Integer.compare(this.eatenBefore, o.eatenBefore);
+ }
+
+ public int getEatenBefore() {
+ return eatenBefore;
+ }
+}
diff --git a/src/test/java/org/labs/FairnessTest.java b/src/test/java/org/labs/FairnessTest.java
new file mode 100644
index 0000000..7beec40
--- /dev/null
+++ b/src/test/java/org/labs/FairnessTest.java
@@ -0,0 +1,37 @@
+package org.labs;
+
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class FairnessTest {
+
+ @Test
+ void testFairPortionDistribution() throws InterruptedException {
+ int progsCount = 7;
+ int waitersCount = 2;
+ int portions = 10000;
+
+ SpoonManager spoonManager = new SpoonManager(progsCount);
+ Programmer[] programmers = new Programmer[progsCount];
+ WaiterService waiterService = new WaiterService(waitersCount, portions);
+ for (int i = 0; i < progsCount; i++) {
+ programmers[i] = new Programmer(i, spoonManager, waiterService);
+ programmers[i].start();
+ }
+
+ for (int i = 0; i < progsCount; i++) {
+ programmers[i].join(20000);
+ }
+
+ double possibleDeviation = 0.1;
+ double average = portions / (double) progsCount;
+ int totalEaten = 0;
+ for (Programmer programmer : programmers) {
+ assertTrue(Math.abs(average - programmer.getEaten()) / average <= possibleDeviation);
+ totalEaten += programmer.getEaten();
+ }
+ waiterService.shutdown();
+
+ assertEquals(portions, totalEaten);
+ }
+}
diff --git a/src/test/java/org/labs/ProgrammerTest.java b/src/test/java/org/labs/ProgrammerTest.java
new file mode 100644
index 0000000..900dbef
--- /dev/null
+++ b/src/test/java/org/labs/ProgrammerTest.java
@@ -0,0 +1,36 @@
+package org.labs;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class ProgrammerTest {
+
+ @Test
+ void testEarlyInterruption() throws InterruptedException {
+ int progsCount = 7;
+ int waitersCount = 2;
+ int portions = 10000;
+
+ SpoonManager spoonManager = new SpoonManager(progsCount);
+ Programmer[] programmers = new Programmer[progsCount];
+ WaiterService waiterService = new WaiterService(waitersCount, portions);
+ for (int i = 0; i < progsCount; i++) {
+ programmers[i] = new Programmer(i, spoonManager, waiterService);
+ programmers[i].start();
+ }
+
+ Thread.sleep(1000);
+
+ int totalEaten = 0;
+ for (Programmer programmer : programmers) {
+ programmer.interrupt();
+ programmer.join();
+ totalEaten += programmer.getEaten();
+ }
+
+ waiterService.shutdown();
+
+ assertTrue(totalEaten < portions);
+ }
+}
diff --git a/src/test/java/org/labs/WaiterTest.java b/src/test/java/org/labs/WaiterTest.java
new file mode 100644
index 0000000..1e0ca5f
--- /dev/null
+++ b/src/test/java/org/labs/WaiterTest.java
@@ -0,0 +1,31 @@
+package org.labs;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.PriorityBlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class WaiterTest {
+
+ @Test
+ public void testWaiterPriorityQueue() throws InterruptedException {
+ PriorityBlockingQueue queue = new PriorityBlockingQueue<>();
+ AtomicInteger portions = new AtomicInteger(100);
+
+ queue.offer(createTask(10, portions));
+ queue.offer(createTask(5, portions));
+ queue.offer(createTask(15, portions));
+
+ assertEquals(5, queue.take().getEatenBefore());
+ assertEquals(10, queue.take().getEatenBefore());
+ assertEquals(15, queue.take().getEatenBefore());
+ }
+
+
+ private WaiterTask createTask(int eatenBefore, AtomicInteger portions) {
+ return new WaiterTask(eatenBefore, portions, new CompletableFuture<>());
+ }
+}
\ No newline at end of file