From e6f6d79014c3fea6b83bfed2b363c9ec488b2b7f Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Wed, 24 Sep 2025 17:02:47 +0000 Subject: [PATCH 1/4] add deadline --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d7a6ba3..e974d43 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-22041afd0340ce965d47ae6ef1cefeee28c7c493a6346c4f15d667ab976d596c.svg)](https://classroom.github.com/a/qcWcnElX) # Java concurrency # Цели и задачи л/р: From a15c519be581a20a8e3204d8312eb7be0b20a59a Mon Sep 17 00:00:00 2001 From: Daniil Vinichenko Date: Thu, 2 Oct 2025 11:55:44 +0300 Subject: [PATCH 2/4] init commit --- .idea/.gitignore | 3 + .idea/.name | 1 + .idea/gradle.xml | 17 +++ .idea/misc.xml | 5 + .idea/vcs.xml | 6 + src/main/java/org/labs/Main.java | 84 ++++++++++- src/main/java/org/labs/Programmer.java | 139 ++++++++++++++++++ src/main/java/org/labs/RationShelf.java | 27 ++++ src/main/java/org/labs/Spoon.java | 32 ++++ src/main/java/org/labs/TaskStatic.java | 62 ++++++++ src/main/java/org/labs/TwoSpoons.java | 11 ++ src/main/java/org/labs/Waiters.java | 19 +++ .../java/org/labs/DinnerSimulationTest.java | 75 ++++++++++ src/test/java/org/labs/Results.java | 13 ++ src/test/java/org/labs/TestCaseSettings.java | 30 ++++ 15 files changed, 522 insertions(+), 2 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/.name create mode 100644 .idea/gradle.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml create mode 100644 src/main/java/org/labs/Programmer.java create mode 100644 src/main/java/org/labs/RationShelf.java create mode 100644 src/main/java/org/labs/Spoon.java create mode 100644 src/main/java/org/labs/TaskStatic.java create mode 100644 src/main/java/org/labs/TwoSpoons.java create mode 100644 src/main/java/org/labs/Waiters.java create mode 100644 src/test/java/org/labs/DinnerSimulationTest.java create mode 100644 src/test/java/org/labs/Results.java create mode 100644 src/test/java/org/labs/TestCaseSettings.java diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..a0a9a28 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +parallel-lab11 \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..48b5565 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..61a27d7 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/main/java/org/labs/Main.java b/src/main/java/org/labs/Main.java index 9917247..11146b8 100644 --- a/src/main/java/org/labs/Main.java +++ b/src/main/java/org/labs/Main.java @@ -1,7 +1,87 @@ package org.labs; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.LongAdder; +import java.util.Arrays; + public class Main { - public static void main(String[] args) { - System.out.println("Hello, World!"); + public record Result(long[] eatenByProgrammer, long totalEaten, long rationsLeft) { } + + public static void main(String[] args) throws InterruptedException { + TaskStatic ts = new TaskStatic( // + 1, // programmersCount + 1, // waitersCount + 20, // rationsCount + 1, // eatTimeMillisMin + 3, // eatTimeMillisMax + 1, // talkTimeMillisMin + 3 // talkTimeMillisMax + ); + + Result r = startDinner(ts); + + Map rationEatenStats = new LinkedHashMap<>(); + for (int i = 0; i < r.eatenByProgrammer.length; i++) { + rationEatenStats.put(i, r.eatenByProgrammer[i]); + } + + System.out.println("Rations eaten by each programmer - " + rationEatenStats); + System.out.println("Rations eaten totally - " + r.totalEaten); + System.out.println("Rations left - " + r.rationsLeft); + } + + public static Result startDinner(TaskStatic ts) throws InterruptedException { + RationShelf rs = new RationShelf(ts.getRationsCount()); + Waiters w = new Waiters(ts.getWaitersCount()); + + int n = ts.getProgrammersCount(); + + LongAdder[] la = new LongAdder[n]; + Arrays.setAll(la, _ -> new LongAdder()); + + Spoon[] s = new Spoon[n]; + for (int i = 0; i < n; i++) { + s[i] = new Spoon(i); + } + + ExecutorService programmersExec = Executors.newFixedThreadPool(n); + List programmersList = new ArrayList<>(n); + + try { + for (int i = 0; i < n; i++) { + Spoon l = s[i]; + Spoon r = s[(i + 1) % n]; + Programmer p = new Programmer(l, r, rs, w, ts, la[i]); + programmersList.add(p); + programmersExec.submit(p); + } + + while (rs.getRationleft().get() > 0) { + Thread.sleep(TaskStatic.ProjectConfig.WAIT_UNTIL_RATION_SHELF_IS_EMPTY); + } + } finally { + for (Programmer p : programmersList) { + p.stop(); + } + programmersExec.shutdownNow(); + programmersExec.awaitTermination(3, TimeUnit.SECONDS); + } + + long[] eaten = new long[n]; + long total = 0; + for (int i = 0; i < n; i++) { + eaten[i] = la[i].sum(); + total += eaten[i]; + } + long left = rs.getRationleft().get(); + + return new Result(eaten, total, left); } + } \ 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..ffbdf57 --- /dev/null +++ b/src/main/java/org/labs/Programmer.java @@ -0,0 +1,139 @@ +package org.labs; + +import java.util.Random; +import java.util.concurrent.atomic.LongAdder; + +public class Programmer implements Runnable { + private final Spoon leftSpoon; + private final Spoon rightSpoon; + private final RationShelf rs; + private final Waiters w; + private final TaskStatic ts; + private final Random r; + private final LongAdder stats; + private volatile boolean running = true; + + public Programmer( + Spoon leftSpoon, + Spoon rightSpoon, + RationShelf rationShelf, + Waiters waiters, + TaskStatic taskStatic, + LongAdder stats + ) { + this.leftSpoon = leftSpoon; + this.rightSpoon = rightSpoon; + this.rs = rationShelf; + this.w = waiters; + this.ts = taskStatic; + this.stats = stats; + this.r = new Random(); + } + + @Override + public void run() { + while (running) { + try { + talkAboutTeachers(); + + TwoSpoons sp = checkSpoons(); + boolean gotSecond = acquireSpoonsOrBackoff(sp); + if (!gotSecond) { + continue; + } + + try { + boolean portionTaken = takePortionWithWaiter(); + if (!portionTaken) { + running = false; + } + } finally { + returnSpoons(sp); + } + + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + break; + } catch (Throwable t) { + System.out.println("[programmer.run] throwable error - " + t.getMessage()); + } + } + } + + private void talkAboutTeachers() throws InterruptedException { + action(ts.getTalkTimeMillisMin(), ts.getTalkTimeMillisMax()); + } + + private void eatRation() throws InterruptedException { + action(ts.getEatTimeMillisMin(), ts.getEatTimeMillisMax()); + } + + private TwoSpoons checkSpoons() { + if (leftSpoon.getSpoonIndex() < rightSpoon.getSpoonIndex()) { + return new TwoSpoons(leftSpoon, rightSpoon); + } else { + return new TwoSpoons(rightSpoon, leftSpoon); + } + } + + private void action(long minMs, long maxMs) throws InterruptedException { + long span = Math.max(0, maxMs - minMs); + + long ms; + if (span == 0) { + ms = minMs; + } else { + ms = minMs + r.nextLong(span); + } + + if (ms > 0) { + Thread.sleep(ms); + } + } + + private boolean acquireSpoonsOrBackoff(TwoSpoons sp) throws InterruptedException { + sp.first.use(); + boolean gotSecond; + try { + long waitMs = TaskStatic.ProjectConfig.PROGRAMMER_WAIT_TIME; + gotSecond = sp.second.useTimeouted(waitMs); + if (!gotSecond) { + sp.first.put(); + tryLater(); + return false; + } + return true; + } catch (InterruptedException ie) { + sp.first.put(); + throw ie; + } + } + + private boolean takePortionWithWaiter() throws InterruptedException { + w.takeWaiter(); + try { + if (!rs.eatRation()) { + return false; + } + eatRation(); + stats.increment(); + return true; + } finally { + w.letWaiterGo(); + } + } + + private void returnSpoons(TwoSpoons sp) { + sp.second.put(); + sp.first.put(); + } + + public void stop() { + running = false; + } + + private void tryLater() throws InterruptedException { + Thread.sleep(r.nextInt(1, TaskStatic.ProjectConfig.PROGRAMMER_WAIT_TIME + 1)); + } +} + diff --git a/src/main/java/org/labs/RationShelf.java b/src/main/java/org/labs/RationShelf.java new file mode 100644 index 0000000..3cf8f96 --- /dev/null +++ b/src/main/java/org/labs/RationShelf.java @@ -0,0 +1,27 @@ +package org.labs; + +import java.util.concurrent.atomic.AtomicLong; + +public class RationShelf { + private final AtomicLong rationleft; + + public RationShelf(long rationleft) { + this.rationleft = new AtomicLong(rationleft); + } + + public boolean eatRation() { + while (true) { + long curCount = rationleft.get(); + if (curCount <= 0) { + return false; + } + if (rationleft.compareAndSet(curCount, curCount - 1)) { + return true; + } + } + } + + public AtomicLong getRationleft() { + return rationleft; + } +} diff --git a/src/main/java/org/labs/Spoon.java b/src/main/java/org/labs/Spoon.java new file mode 100644 index 0000000..b850508 --- /dev/null +++ b/src/main/java/org/labs/Spoon.java @@ -0,0 +1,32 @@ +package org.labs; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +public class Spoon { + private final int spoonIndex; + private final ReentrantLock lock; + + public Spoon(int spoonIndex) { + this.spoonIndex = spoonIndex; + this.lock = new ReentrantLock(true); + } + + public int getSpoonIndex() { + return spoonIndex; + } + + public boolean useTimeouted(long timeoutMillis) throws InterruptedException { + return lock.tryLock(timeoutMillis, TimeUnit.MILLISECONDS); + } + + public void use() { + lock.lock(); + } + + public void put() { + if (lock.isHeldByCurrentThread()) { + lock.unlock(); + } + } +} diff --git a/src/main/java/org/labs/TaskStatic.java b/src/main/java/org/labs/TaskStatic.java new file mode 100644 index 0000000..3a00fbf --- /dev/null +++ b/src/main/java/org/labs/TaskStatic.java @@ -0,0 +1,62 @@ +package org.labs; + +public class TaskStatic { + private final int programmersCount; + private final int waitersCount; + private final int rationsCount; + private final long eatTimeMillisMin; + private final long eatTimeMillisMax; + private final long talkTimeMillisMin; + private final long talkTimeMillisMax; + + public TaskStatic( + int programmersCount, + int waitersCount, + int rationsCount, + long eatTimeMillisMin, + long eatTimeMillisMax, + long talkTimeMillisMin, + long talkTimeMillisMax + ) { + this.programmersCount = programmersCount; + this.waitersCount = waitersCount; + this.rationsCount = rationsCount; + this.eatTimeMillisMin = eatTimeMillisMin; + this.eatTimeMillisMax = eatTimeMillisMax; + this.talkTimeMillisMin = talkTimeMillisMin; + this.talkTimeMillisMax = talkTimeMillisMax; + } + + public int getProgrammersCount() { + return programmersCount; + } + + public int getWaitersCount() { + return waitersCount; + } + + public int getRationsCount() { + return rationsCount; + } + + public long getEatTimeMillisMin() { + return eatTimeMillisMin; + } + + public long getEatTimeMillisMax() { + return eatTimeMillisMax; + } + + public long getTalkTimeMillisMin() { + return talkTimeMillisMin; + } + + public long getTalkTimeMillisMax() { + return talkTimeMillisMax; + } + + public static class ProjectConfig { + public static final int PROGRAMMER_WAIT_TIME = 5; + public static final int WAIT_UNTIL_RATION_SHELF_IS_EMPTY = 30; + } +} diff --git a/src/main/java/org/labs/TwoSpoons.java b/src/main/java/org/labs/TwoSpoons.java new file mode 100644 index 0000000..a1af7d3 --- /dev/null +++ b/src/main/java/org/labs/TwoSpoons.java @@ -0,0 +1,11 @@ +package org.labs; + +public class TwoSpoons { + final Spoon first; + final Spoon second; + + TwoSpoons(Spoon first, Spoon second) { + this.first = first; + this.second = second; + } +} \ No newline at end of file diff --git a/src/main/java/org/labs/Waiters.java b/src/main/java/org/labs/Waiters.java new file mode 100644 index 0000000..06c8aab --- /dev/null +++ b/src/main/java/org/labs/Waiters.java @@ -0,0 +1,19 @@ +package org.labs; + +import java.util.concurrent.Semaphore; + +public class Waiters { + private final Semaphore semaphore; + + public Waiters(int count) { + this.semaphore = new Semaphore(count, true); + } + + public void takeWaiter() throws InterruptedException { + semaphore.acquire(); + } + + public void letWaiterGo() { + semaphore.release(); + } +} diff --git a/src/test/java/org/labs/DinnerSimulationTest.java b/src/test/java/org/labs/DinnerSimulationTest.java new file mode 100644 index 0000000..1c3e79a --- /dev/null +++ b/src/test/java/org/labs/DinnerSimulationTest.java @@ -0,0 +1,75 @@ +package org.labs; + +import static org.junit.jupiter.api.Assertions.*; + +import java.time.Duration; +import java.util.*; +import java.util.concurrent.*; +import java.util.stream.Stream; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@Tag("dinner") +class DinnerSimulationTest { + + static Stream testCases() { + return Stream.of( + new TestCaseSettings("one-fat-man-1-20", 1, 1, 20, 1, 3, 1, 3), + new TestCaseSettings("5-2-200", 5, 2, 200, 1, 3, 1, 3), + new TestCaseSettings("7-2-300", 7, 2, 300, 2, 5, 2, 5), + new TestCaseSettings("8-5-300", 8, 5, 300, 1, 2, 1, 2), + new TestCaseSettings("10-1-400", 10, 1, 400, 1, 2, 1, 2), + new TestCaseSettings("100-1-4000", 100, 1, 4000, 1, 2, 1, 2) + ); + } + + @Order(1) + @DisplayName("Test ends in time without deadlocks") + @ParameterizedTest(name = "{index} => {0}") + @MethodSource("testCases") + void finishesWithinTimeout(TestCaseSettings sc) { + assertTimeoutPreemptively(Duration.ofSeconds(10), () -> { + Main.Result r = Main.startDinner(sc.toTaskStatic()); + assertNotNull(r); + }); + } + + @Order(2) + @DisplayName("Ration counts correctly, no over/underusage") + @ParameterizedTest(name = "{index} => {0}") + @MethodSource("testCases") + void allRationsAccounted(TestCaseSettings sc) throws InterruptedException { + Main.Result r = Main.startDinner(sc.toTaskStatic()); + long sumEaten = Arrays.stream(r.eatenByProgrammer()).sum(); + long left = r.rationsLeft(); + + assertEquals(0L, left, "Should be zero rations left"); + assertEquals(sc.rations, sumEaten, "Eaten sum must equal total rations"); + } + + @Order(3) + @DisplayName("Everyone eats almost the same amount of rations (contention-aware)") + @ParameterizedTest(name = "{index} => {0}") + @MethodSource("testCases") + void fairnessIsReasonable(TestCaseSettings sc) throws InterruptedException { + Main.Result r = Main.startDinner(sc.toTaskStatic()); + long[] arr = r.eatenByProgrammer().clone(); + Arrays.sort(arr); + long min = arr[0]; + long max = arr[arr.length - 1]; + + double sigma = Math.sqrt(sc.rations / (double) sc.programmers); + double allowedDelta = 4 * sigma; + + assertTrue((max - min) <= allowedDelta, + String.format("Wide dispersion: min=%d, max=%d, mean≈%.2f, sigma≈%.2f, allowed≤%.2f", + min, max, sc.rations / (double) sc.programmers, sigma, allowedDelta)); + } +} diff --git a/src/test/java/org/labs/Results.java b/src/test/java/org/labs/Results.java new file mode 100644 index 0000000..cf53331 --- /dev/null +++ b/src/test/java/org/labs/Results.java @@ -0,0 +1,13 @@ +package org.labs; + +public final class Results { + final long[] eatenByProgrammer; + final long rationsLeft; + final long totalEaten; + + Results(long[] eatenByProgrammer, long rationsLeft, long totalEaten) { + this.eatenByProgrammer = eatenByProgrammer; + this.rationsLeft = rationsLeft; + this.totalEaten = totalEaten; + } +} \ No newline at end of file diff --git a/src/test/java/org/labs/TestCaseSettings.java b/src/test/java/org/labs/TestCaseSettings.java new file mode 100644 index 0000000..4f20e8c --- /dev/null +++ b/src/test/java/org/labs/TestCaseSettings.java @@ -0,0 +1,30 @@ +package org.labs; + +public final class TestCaseSettings { + final String name; + final int programmers; + final int waiters; + final int rations; + final int eatMin, eatMax; + final int talkMin, talkMax; + + TestCaseSettings(String name, int programmers, int waiters, int rations, + int eatMin, int eatMax, int talkMin, int talkMax) { + this.name = name; + this.programmers = programmers; + this.waiters = waiters; + this.rations = rations; + this.eatMin = eatMin; + this.eatMax = eatMax; + this.talkMin = talkMin; + this.talkMax = talkMax; + } + + TaskStatic toTaskStatic() { + return new TaskStatic(programmers, waiters, rations, eatMin, eatMax, talkMin, talkMax); + } + + @Override public String toString() { + return String.format("%s[p=%d,w=%d,r=%d]", name, programmers, waiters, rations); + } +} \ No newline at end of file From 5a116b5cf396d2b72b9081ffb3e5ce23d66df430 Mon Sep 17 00:00:00 2001 From: Daniil Vinichenko Date: Thu, 2 Oct 2025 16:51:21 +0300 Subject: [PATCH 3/4] javadocs and some changes --- src/main/java/org/labs/Main.java | 74 +++++++++++-------- src/main/java/org/labs/Programmer.java | 52 +++++++++++-- src/main/java/org/labs/RationShelf.java | 27 +++++-- src/main/java/org/labs/Result.java | 22 ++++++ src/main/java/org/labs/Spoon.java | 2 +- src/main/java/org/labs/TaskStatic.java | 4 +- src/main/java/org/labs/TwoSpoons.java | 3 + src/main/java/org/labs/Waiters.java | 10 +++ .../java/org/labs/DinnerSimulationTest.java | 14 ++-- src/test/java/org/labs/Results.java | 13 ---- src/test/java/org/labs/TestCaseSettings.java | 17 ++++- 11 files changed, 165 insertions(+), 73 deletions(-) create mode 100644 src/main/java/org/labs/Result.java delete mode 100644 src/test/java/org/labs/Results.java diff --git a/src/main/java/org/labs/Main.java b/src/main/java/org/labs/Main.java index 11146b8..1218701 100644 --- a/src/main/java/org/labs/Main.java +++ b/src/main/java/org/labs/Main.java @@ -11,8 +11,12 @@ import java.util.Arrays; public class Main { - public record Result(long[] eatenByProgrammer, long totalEaten, long rationsLeft) { } - + /** + * Точка входа в программу. Здесь задаются параметры выполняемыой программы (см. TaskStatic), вызывается выполнение программы, + * собираются результаты и выводятся в консоль + * @param args Не используется, так как статика задаётся хард-кодом внутри самого метода + * @throws InterruptedException Просто висит + */ public static void main(String[] args) throws InterruptedException { TaskStatic ts = new TaskStatic( // 1, // programmersCount @@ -27,15 +31,22 @@ public static void main(String[] args) throws InterruptedException { Result r = startDinner(ts); Map rationEatenStats = new LinkedHashMap<>(); - for (int i = 0; i < r.eatenByProgrammer.length; i++) { - rationEatenStats.put(i, r.eatenByProgrammer[i]); + for (int i = 0; i < r.eatenByProgrammer().length; i++) { + rationEatenStats.put(i, r.eatenByProgrammer()[i]); } System.out.println("Rations eaten by each programmer - " + rationEatenStats); - System.out.println("Rations eaten totally - " + r.totalEaten); - System.out.println("Rations left - " + r.rationsLeft); + System.out.println("Rations eaten totally - " + r.totalEaten()); + System.out.println("Rations left - " + r.rationsLeft()); } + /** + * Основной выполняющий метод, вызывается из мейна. Здесь задаются необходимые объекты, по типу ложек и шкафа с едой + * и происходит само выполнение программы, с досугом рационного возъедания и болтовнёй + * @param ts Настройки программы (статика) + * @return Результат обеда + * @throws InterruptedException Зачем-то пробрасывается + */ public static Result startDinner(TaskStatic ts) throws InterruptedException { RationShelf rs = new RationShelf(ts.getRationsCount()); Waiters w = new Waiters(ts.getWaitersCount()); @@ -50,38 +61,39 @@ public static Result startDinner(TaskStatic ts) throws InterruptedException { s[i] = new Spoon(i); } - ExecutorService programmersExec = Executors.newFixedThreadPool(n); - List programmersList = new ArrayList<>(n); - - try { - for (int i = 0; i < n; i++) { - Spoon l = s[i]; - Spoon r = s[(i + 1) % n]; - Programmer p = new Programmer(l, r, rs, w, ts, la[i]); - programmersList.add(p); - programmersExec.submit(p); - } - - while (rs.getRationleft().get() > 0) { - Thread.sleep(TaskStatic.ProjectConfig.WAIT_UNTIL_RATION_SHELF_IS_EMPTY); - } - } finally { - for (Programmer p : programmersList) { - p.stop(); + try (ExecutorService programmersExec = Executors.newFixedThreadPool(n)) { + List programmersList = new ArrayList<>(n); + + try { + for (int i = 0; i < n; i++) { + Spoon l = s[i]; + Spoon r = s[(i + 1) % n]; + Programmer p = new Programmer(l, r, rs, w, ts, la[i]); + programmersList.add(p); + programmersExec.submit(p); + } + + while (rs.getLeftRations().get() > 0) { + Thread.sleep(TaskStatic.ProjectConfig.WAIT_UNTIL_RATION_SHELF_IS_EMPTY); + } + } finally { + for (Programmer p : programmersList) { + p.stop(); + } + programmersExec.shutdownNow(); + programmersExec.awaitTermination(3, TimeUnit.SECONDS); } - programmersExec.shutdownNow(); - programmersExec.awaitTermination(3, TimeUnit.SECONDS); } - long[] eaten = new long[n]; + long[] ratinosEaten = new long[n]; long total = 0; for (int i = 0; i < n; i++) { - eaten[i] = la[i].sum(); - total += eaten[i]; + ratinosEaten[i] = la[i].sum(); + total += ratinosEaten[i]; } - long left = rs.getRationleft().get(); + long left = rs.getLeftRations().get(); - return new Result(eaten, total, left); + return new Result(ratinosEaten, total, left); } } \ No newline at end of file diff --git a/src/main/java/org/labs/Programmer.java b/src/main/java/org/labs/Programmer.java index ffbdf57..c07da7e 100644 --- a/src/main/java/org/labs/Programmer.java +++ b/src/main/java/org/labs/Programmer.java @@ -3,6 +3,9 @@ import java.util.Random; import java.util.concurrent.atomic.LongAdder; +/** + * Класс программистов, которые будут конкурентно есть еду + */ public class Programmer implements Runnable { private final Spoon leftSpoon; private final Spoon rightSpoon; @@ -30,6 +33,9 @@ public Programmer( this.r = new Random(); } + /** + * Метод поедания еды программистом, здесь происходит периодическая болтовня, взятие ложек и поедание рационов + */ @Override public void run() { while (running) { @@ -60,14 +66,26 @@ public void run() { } } + /** + * Метод для болтовни, программист будет болтать какое-то случайное время, в рамках заданных настроек + * @throws InterruptedException + */ private void talkAboutTeachers() throws InterruptedException { action(ts.getTalkTimeMillisMin(), ts.getTalkTimeMillisMax()); } + /** + * То же самое, что и talkAboutTeachers, только для поедания еды (работает с другим временем) + * @throws InterruptedException + */ private void eatRation() throws InterruptedException { action(ts.getEatTimeMillisMin(), ts.getEatTimeMillisMax()); } + /** + * Метод проверки, что программист будет брать две ложки, которые лежат рядом с ним + * @return Объект-хелпер с двумя ложками, которые может взять программист + */ private TwoSpoons checkSpoons() { if (leftSpoon.getSpoonIndex() < rightSpoon.getSpoonIndex()) { return new TwoSpoons(leftSpoon, rightSpoon); @@ -76,6 +94,12 @@ private TwoSpoons checkSpoons() { } } + /** + * Метод выполнения каког-то действия - есть или болтать + * @param minMs Минимальное время выполнения действия + * @param maxMs Максимальное время выполнения действия + * @throws InterruptedException + */ private void action(long minMs, long maxMs) throws InterruptedException { long span = Math.max(0, maxMs - minMs); @@ -91,15 +115,21 @@ private void action(long minMs, long maxMs) throws InterruptedException { } } + /** + * Метод для взятия ложек, проверяем можем ли мы взять обе ложки в каком-то временном интервале. + * Если нет, то возвращаем обе ложки + * @param sp Набор ложек, которые мы можем взять + * @return исход взятия обеих ложек (true если взяли обе + * @throws InterruptedException + */ private boolean acquireSpoonsOrBackoff(TwoSpoons sp) throws InterruptedException { sp.first.use(); boolean gotSecond; try { long waitMs = TaskStatic.ProjectConfig.PROGRAMMER_WAIT_TIME; - gotSecond = sp.second.useTimeouted(waitMs); + gotSecond = sp.second.useWithTimeOut(waitMs); if (!gotSecond) { sp.first.put(); - tryLater(); return false; } return true; @@ -109,10 +139,15 @@ private boolean acquireSpoonsOrBackoff(TwoSpoons sp) throws InterruptedException } } + /** + * Возможность позвать свободного оффицианта и взять у него рацион + * @return Исход возможности взятия рациона + * @throws InterruptedException + */ private boolean takePortionWithWaiter() throws InterruptedException { w.takeWaiter(); try { - if (!rs.eatRation()) { + if (!rs.takeRation()) { return false; } eatRation(); @@ -123,17 +158,20 @@ private boolean takePortionWithWaiter() throws InterruptedException { } } + /** + * Возврат обеих ложек + * @param sp Набор ложек конкретного программиста + */ private void returnSpoons(TwoSpoons sp) { sp.second.put(); sp.first.put(); } + /** + * Стоп выполнения досуга поедания рациона + */ public void stop() { running = false; } - - private void tryLater() throws InterruptedException { - Thread.sleep(r.nextInt(1, TaskStatic.ProjectConfig.PROGRAMMER_WAIT_TIME + 1)); - } } diff --git a/src/main/java/org/labs/RationShelf.java b/src/main/java/org/labs/RationShelf.java index 3cf8f96..d71ebbd 100644 --- a/src/main/java/org/labs/RationShelf.java +++ b/src/main/java/org/labs/RationShelf.java @@ -2,26 +2,37 @@ import java.util.concurrent.atomic.AtomicLong; +/** + * Шкаф со всеми рационами + */ public class RationShelf { - private final AtomicLong rationleft; + private final AtomicLong rationLeft; - public RationShelf(long rationleft) { - this.rationleft = new AtomicLong(rationleft); + public RationShelf(long rationLeft) { + this.rationLeft = new AtomicLong(rationLeft); } - public boolean eatRation() { + /** + * Метод для взятия рациона, если ещё есть остатки + * @return Исход попытки взятия рациона + */ + public boolean takeRation() { while (true) { - long curCount = rationleft.get(); + long curCount = rationLeft.get(); if (curCount <= 0) { return false; } - if (rationleft.compareAndSet(curCount, curCount - 1)) { + if (rationLeft.compareAndSet(curCount, curCount - 1)) { return true; } } } - public AtomicLong getRationleft() { - return rationleft; + /** + * Получить число оставшихся рационов + * @return Число оставшихся рационов + */ + public AtomicLong getLeftRations() { + return rationLeft; } } diff --git a/src/main/java/org/labs/Result.java b/src/main/java/org/labs/Result.java new file mode 100644 index 0000000..f1ecf11 --- /dev/null +++ b/src/main/java/org/labs/Result.java @@ -0,0 +1,22 @@ +package org.labs; + +public record Result( + long[] eatenByProgrammer, + long totalEaten, + long rationsLeft +) { + @Override + public long[] eatenByProgrammer() { + return eatenByProgrammer; + } + + @Override + public long totalEaten() { + return totalEaten; + } + + @Override + public long rationsLeft() { + return rationsLeft; + } +} diff --git a/src/main/java/org/labs/Spoon.java b/src/main/java/org/labs/Spoon.java index b850508..090061a 100644 --- a/src/main/java/org/labs/Spoon.java +++ b/src/main/java/org/labs/Spoon.java @@ -16,7 +16,7 @@ public int getSpoonIndex() { return spoonIndex; } - public boolean useTimeouted(long timeoutMillis) throws InterruptedException { + public boolean useWithTimeOut(long timeoutMillis) throws InterruptedException { return lock.tryLock(timeoutMillis, TimeUnit.MILLISECONDS); } diff --git a/src/main/java/org/labs/TaskStatic.java b/src/main/java/org/labs/TaskStatic.java index 3a00fbf..c01e534 100644 --- a/src/main/java/org/labs/TaskStatic.java +++ b/src/main/java/org/labs/TaskStatic.java @@ -56,7 +56,7 @@ public long getTalkTimeMillisMax() { } public static class ProjectConfig { - public static final int PROGRAMMER_WAIT_TIME = 5; - public static final int WAIT_UNTIL_RATION_SHELF_IS_EMPTY = 30; + public static final int PROGRAMMER_WAIT_TIME = 3; + public static final int WAIT_UNTIL_RATION_SHELF_IS_EMPTY = 50; } } diff --git a/src/main/java/org/labs/TwoSpoons.java b/src/main/java/org/labs/TwoSpoons.java index a1af7d3..73344dd 100644 --- a/src/main/java/org/labs/TwoSpoons.java +++ b/src/main/java/org/labs/TwoSpoons.java @@ -1,5 +1,8 @@ package org.labs; +/** + * Небольшой хелпер, где лежат ложки, которые может взять или уже взял какой-то программист + */ public class TwoSpoons { final Spoon first; final Spoon second; diff --git a/src/main/java/org/labs/Waiters.java b/src/main/java/org/labs/Waiters.java index 06c8aab..b28c326 100644 --- a/src/main/java/org/labs/Waiters.java +++ b/src/main/java/org/labs/Waiters.java @@ -2,6 +2,9 @@ import java.util.concurrent.Semaphore; +/** + * Пулл оффициантов, которых можно занимать для прошения еды + */ public class Waiters { private final Semaphore semaphore; @@ -9,10 +12,17 @@ public Waiters(int count) { this.semaphore = new Semaphore(count, true); } + /** + * Занять оффицианта + * @throws InterruptedException + */ public void takeWaiter() throws InterruptedException { semaphore.acquire(); } + /** + * Освободить оффицианта + */ public void letWaiterGo() { semaphore.release(); } diff --git a/src/test/java/org/labs/DinnerSimulationTest.java b/src/test/java/org/labs/DinnerSimulationTest.java index 1c3e79a..df842b9 100644 --- a/src/test/java/org/labs/DinnerSimulationTest.java +++ b/src/test/java/org/labs/DinnerSimulationTest.java @@ -21,12 +21,12 @@ class DinnerSimulationTest { static Stream testCases() { return Stream.of( - new TestCaseSettings("one-fat-man-1-20", 1, 1, 20, 1, 3, 1, 3), + new TestCaseSettings("1-1-20", 1, 1, 20, 1, 3, 1, 3), new TestCaseSettings("5-2-200", 5, 2, 200, 1, 3, 1, 3), new TestCaseSettings("7-2-300", 7, 2, 300, 2, 5, 2, 5), new TestCaseSettings("8-5-300", 8, 5, 300, 1, 2, 1, 2), - new TestCaseSettings("10-1-400", 10, 1, 400, 1, 2, 1, 2), - new TestCaseSettings("100-1-4000", 100, 1, 4000, 1, 2, 1, 2) + new TestCaseSettings("10-1-400", 10, 1, 400, 1, 2, 1, 2)// , + // new TestCaseSettings("7-2-10000", 7, 2, 100000, 1, 2, 1, 2) ); } @@ -35,8 +35,8 @@ static Stream testCases() { @ParameterizedTest(name = "{index} => {0}") @MethodSource("testCases") void finishesWithinTimeout(TestCaseSettings sc) { - assertTimeoutPreemptively(Duration.ofSeconds(10), () -> { - Main.Result r = Main.startDinner(sc.toTaskStatic()); + assertTimeoutPreemptively(Duration.ofSeconds(240), () -> { + Result r = Main.startDinner(sc.toTaskStatic()); assertNotNull(r); }); } @@ -46,7 +46,7 @@ void finishesWithinTimeout(TestCaseSettings sc) { @ParameterizedTest(name = "{index} => {0}") @MethodSource("testCases") void allRationsAccounted(TestCaseSettings sc) throws InterruptedException { - Main.Result r = Main.startDinner(sc.toTaskStatic()); + Result r = Main.startDinner(sc.toTaskStatic()); long sumEaten = Arrays.stream(r.eatenByProgrammer()).sum(); long left = r.rationsLeft(); @@ -59,7 +59,7 @@ void allRationsAccounted(TestCaseSettings sc) throws InterruptedException { @ParameterizedTest(name = "{index} => {0}") @MethodSource("testCases") void fairnessIsReasonable(TestCaseSettings sc) throws InterruptedException { - Main.Result r = Main.startDinner(sc.toTaskStatic()); + Result r = Main.startDinner(sc.toTaskStatic()); long[] arr = r.eatenByProgrammer().clone(); Arrays.sort(arr); long min = arr[0]; diff --git a/src/test/java/org/labs/Results.java b/src/test/java/org/labs/Results.java deleted file mode 100644 index cf53331..0000000 --- a/src/test/java/org/labs/Results.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.labs; - -public final class Results { - final long[] eatenByProgrammer; - final long rationsLeft; - final long totalEaten; - - Results(long[] eatenByProgrammer, long rationsLeft, long totalEaten) { - this.eatenByProgrammer = eatenByProgrammer; - this.rationsLeft = rationsLeft; - this.totalEaten = totalEaten; - } -} \ No newline at end of file diff --git a/src/test/java/org/labs/TestCaseSettings.java b/src/test/java/org/labs/TestCaseSettings.java index 4f20e8c..99f5c31 100644 --- a/src/test/java/org/labs/TestCaseSettings.java +++ b/src/test/java/org/labs/TestCaseSettings.java @@ -8,8 +8,16 @@ public final class TestCaseSettings { final int eatMin, eatMax; final int talkMin, talkMax; - TestCaseSettings(String name, int programmers, int waiters, int rations, - int eatMin, int eatMax, int talkMin, int talkMax) { + TestCaseSettings( + String name, + int programmers, + int waiters, + int rations, + int eatMin, + int eatMax, + int talkMin, + int talkMax + ) { this.name = name; this.programmers = programmers; this.waiters = waiters; @@ -20,11 +28,12 @@ public final class TestCaseSettings { this.talkMax = talkMax; } - TaskStatic toTaskStatic() { + public TaskStatic toTaskStatic() { return new TaskStatic(programmers, waiters, rations, eatMin, eatMax, talkMin, talkMax); } - @Override public String toString() { + @Override + public String toString() { return String.format("%s[p=%d,w=%d,r=%d]", name, programmers, waiters, rations); } } \ No newline at end of file From ada82cd076ff3cee9a01bb846590d30a2830ebb6 Mon Sep 17 00:00:00 2001 From: Daniil Vinichenko Date: Thu, 16 Oct 2025 16:25:38 +0300 Subject: [PATCH 4/4] priority queue and other changes --- src/main/java/org/labs/Main.java | 31 ++++--- src/main/java/org/labs/Programmer.java | 84 ++++++++++--------- src/main/java/org/labs/QueueNode.java | 16 ++++ src/main/java/org/labs/Spoon.java | 4 - src/main/java/org/labs/Waiters.java | 67 +++++++++++++-- .../java/org/labs/DinnerSimulationTest.java | 31 +++---- 6 files changed, 149 insertions(+), 84 deletions(-) create mode 100644 src/main/java/org/labs/QueueNode.java diff --git a/src/main/java/org/labs/Main.java b/src/main/java/org/labs/Main.java index 1218701..82da43f 100644 --- a/src/main/java/org/labs/Main.java +++ b/src/main/java/org/labs/Main.java @@ -20,7 +20,7 @@ public class Main { public static void main(String[] args) throws InterruptedException { TaskStatic ts = new TaskStatic( // 1, // programmersCount - 1, // waitersCount + 1, // waitersCount 20, // rationsCount 1, // eatTimeMillisMin 3, // eatTimeMillisMax @@ -49,25 +49,25 @@ public static void main(String[] args) throws InterruptedException { */ public static Result startDinner(TaskStatic ts) throws InterruptedException { RationShelf rs = new RationShelf(ts.getRationsCount()); - Waiters w = new Waiters(ts.getWaitersCount()); + Waiters w = new Waiters(ts.getWaitersCount(), ts.getProgrammersCount()); - int n = ts.getProgrammersCount(); + int programmersCount = ts.getProgrammersCount(); - LongAdder[] la = new LongAdder[n]; + LongAdder[] la = new LongAdder[programmersCount]; Arrays.setAll(la, _ -> new LongAdder()); - Spoon[] s = new Spoon[n]; - for (int i = 0; i < n; i++) { + Spoon[] s = new Spoon[programmersCount]; + for (int i = 0; i < programmersCount; i++) { s[i] = new Spoon(i); } - try (ExecutorService programmersExec = Executors.newFixedThreadPool(n)) { - List programmersList = new ArrayList<>(n); + try (ExecutorService programmersExec = Executors.newVirtualThreadPerTaskExecutor()) { + List programmersList = new ArrayList<>(programmersCount); try { - for (int i = 0; i < n; i++) { + for (int i = 0; i < programmersCount; i++) { Spoon l = s[i]; - Spoon r = s[(i + 1) % n]; + Spoon r = s[(i + 1) % programmersCount]; Programmer p = new Programmer(l, r, rs, w, ts, la[i]); programmersList.add(p); programmersExec.submit(p); @@ -81,19 +81,18 @@ public static Result startDinner(TaskStatic ts) throws InterruptedException { p.stop(); } programmersExec.shutdownNow(); - programmersExec.awaitTermination(3, TimeUnit.SECONDS); } } - long[] ratinosEaten = new long[n]; + long[] rationsEaten = new long[programmersCount]; long total = 0; - for (int i = 0; i < n; i++) { - ratinosEaten[i] = la[i].sum(); - total += ratinosEaten[i]; + for (int i = 0; i < programmersCount; i++) { + rationsEaten[i] = la[i].sum(); + total += rationsEaten[i]; } long left = rs.getLeftRations().get(); - return new Result(ratinosEaten, total, left); + return new Result(rationsEaten, total, left); } } \ No newline at end of file diff --git a/src/main/java/org/labs/Programmer.java b/src/main/java/org/labs/Programmer.java index c07da7e..9d78d7f 100644 --- a/src/main/java/org/labs/Programmer.java +++ b/src/main/java/org/labs/Programmer.java @@ -1,6 +1,6 @@ package org.labs; -import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.LongAdder; /** @@ -12,7 +12,6 @@ public class Programmer implements Runnable { private final RationShelf rs; private final Waiters w; private final TaskStatic ts; - private final Random r; private final LongAdder stats; private volatile boolean running = true; @@ -30,7 +29,6 @@ public Programmer( this.w = waiters; this.ts = taskStatic; this.stats = stats; - this.r = new Random(); } /** @@ -41,22 +39,24 @@ public void run() { while (running) { try { talkAboutTeachers(); - - TwoSpoons sp = checkSpoons(); - boolean gotSecond = acquireSpoonsOrBackoff(sp); - if (!gotSecond) { - continue; - } - + w.takeWaiter(stats); try { - boolean portionTaken = takePortionWithWaiter(); - if (!portionTaken) { - running = false; + TwoSpoons sp = checkSpoons(); + if (tryTakeSpoons(sp)) { + try { + if (!rs.takeRation()) { + running = false; + continue; + } + eatRation(); + stats.increment(); + } finally { + returnSpoons(sp); + } } } finally { - returnSpoons(sp); + w.letWaiterGo(); } - } catch (InterruptedException ie) { Thread.currentThread().interrupt(); break; @@ -66,6 +66,32 @@ public void run() { } } + /** + * Метод для взятия ложек, проверяем можем ли мы взять обе ложки в каком-то временном интервале. + * Если нет, то возвращаем обе ложки + * @param sp Набор ложек, которые мы можем взять + * @return исход взятия обеих ложек (true если взяли обе + * @throws InterruptedException + */ + private boolean tryTakeSpoons(TwoSpoons sp) throws InterruptedException { + long waitTime = TaskStatic.ProjectConfig.PROGRAMMER_WAIT_TIME; + if (!sp.first.useWithTimeOut(waitTime)) { + return false; + } + boolean gotSecond; + try { + gotSecond = sp.second.useWithTimeOut(waitTime); + if (!gotSecond) { + sp.first.put(); + return false; + } + return true; + } catch (InterruptedException ie) { + sp.first.put(); + throw ie; + } + } + /** * Метод для болтовни, программист будет болтать какое-то случайное время, в рамках заданных настроек * @throws InterruptedException @@ -107,7 +133,7 @@ private void action(long minMs, long maxMs) throws InterruptedException { if (span == 0) { ms = minMs; } else { - ms = minMs + r.nextLong(span); + ms = minMs + ThreadLocalRandom.current().nextLong(span); } if (ms > 0) { @@ -115,37 +141,13 @@ private void action(long minMs, long maxMs) throws InterruptedException { } } - /** - * Метод для взятия ложек, проверяем можем ли мы взять обе ложки в каком-то временном интервале. - * Если нет, то возвращаем обе ложки - * @param sp Набор ложек, которые мы можем взять - * @return исход взятия обеих ложек (true если взяли обе - * @throws InterruptedException - */ - private boolean acquireSpoonsOrBackoff(TwoSpoons sp) throws InterruptedException { - sp.first.use(); - boolean gotSecond; - try { - long waitMs = TaskStatic.ProjectConfig.PROGRAMMER_WAIT_TIME; - gotSecond = sp.second.useWithTimeOut(waitMs); - if (!gotSecond) { - sp.first.put(); - return false; - } - return true; - } catch (InterruptedException ie) { - sp.first.put(); - throw ie; - } - } - /** * Возможность позвать свободного оффицианта и взять у него рацион * @return Исход возможности взятия рациона * @throws InterruptedException */ private boolean takePortionWithWaiter() throws InterruptedException { - w.takeWaiter(); + w.takeWaiter(stats); try { if (!rs.takeRation()) { return false; diff --git a/src/main/java/org/labs/QueueNode.java b/src/main/java/org/labs/QueueNode.java new file mode 100644 index 0000000..28921ae --- /dev/null +++ b/src/main/java/org/labs/QueueNode.java @@ -0,0 +1,16 @@ +package org.labs; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicLong; + +public final class QueueNode { + final long hunger; + final long placeInQ; + final CountDownLatch latch = new CountDownLatch(1); + volatile boolean isReleased; + + QueueNode(long eaten, AtomicLong qOrder) { + this.hunger = eaten; + this.placeInQ = qOrder.getAndIncrement(); + } +} diff --git a/src/main/java/org/labs/Spoon.java b/src/main/java/org/labs/Spoon.java index 090061a..80369a7 100644 --- a/src/main/java/org/labs/Spoon.java +++ b/src/main/java/org/labs/Spoon.java @@ -20,10 +20,6 @@ public boolean useWithTimeOut(long timeoutMillis) throws InterruptedException { return lock.tryLock(timeoutMillis, TimeUnit.MILLISECONDS); } - public void use() { - lock.lock(); - } - public void put() { if (lock.isHeldByCurrentThread()) { lock.unlock(); diff --git a/src/main/java/org/labs/Waiters.java b/src/main/java/org/labs/Waiters.java index b28c326..575d617 100644 --- a/src/main/java/org/labs/Waiters.java +++ b/src/main/java/org/labs/Waiters.java @@ -1,29 +1,78 @@ package org.labs; -import java.util.concurrent.Semaphore; +import java.util.Comparator; +import java.util.concurrent.PriorityBlockingQueue; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.atomic.LongAdder; /** - * Пулл оффициантов, которых можно занимать для прошения еды + * Пулл официантов с приоритетной очередью, обслуживаем сначала самого голодного типочка */ public class Waiters { - private final Semaphore semaphore; + private final AtomicInteger permits; + private final PriorityBlockingQueue queue; + private final ReentrantLock lock = new ReentrantLock(); + private static final AtomicLong qOrder = new AtomicLong(); - public Waiters(int count) { - this.semaphore = new Semaphore(count, true); + public Waiters(int waitersCount, int programmersCount) { + this.permits = new AtomicInteger(waitersCount); + this.queue = new PriorityBlockingQueue<>(programmersCount, + Comparator.comparingLong((QueueNode a) -> a.hunger).thenComparingLong(a -> a.placeInQ)); } /** - * Занять оффицианта + * Занять оффицианта с приоритетом по "голоду". + * @param stats счётчик съеденных рационов у программиста (чем голоднее, тем выше приоритет) * @throws InterruptedException */ - public void takeWaiter() throws InterruptedException { - semaphore.acquire(); + public void takeWaiter(LongAdder stats) throws InterruptedException { + QueueNode QueueNode = new QueueNode(stats.sum(), qOrder); + + lock.lock(); + try { + queue.add(QueueNode); + if (queue.peek() == QueueNode && permits.get() > 0) { + permits.decrementAndGet(); + queue.poll(); + QueueNode.isReleased = true; + QueueNode.latch.countDown(); + } + } finally { + lock.unlock(); + } + + try { + QueueNode.latch.await(); + } catch (InterruptedException ie) { + lock.lock(); + try { + if (!QueueNode.isReleased) { + queue.remove(QueueNode); + throw ie; + } + } finally { + lock.unlock(); + } + } } /** * Освободить оффицианта */ public void letWaiterGo() { - semaphore.release(); + lock.lock(); + try { + QueueNode next = queue.poll(); + if (next != null) { + next.isReleased = true; + next.latch.countDown(); + } else { + permits.incrementAndGet(); + } + } finally { + lock.unlock(); + } } } diff --git a/src/test/java/org/labs/DinnerSimulationTest.java b/src/test/java/org/labs/DinnerSimulationTest.java index df842b9..9f38789 100644 --- a/src/test/java/org/labs/DinnerSimulationTest.java +++ b/src/test/java/org/labs/DinnerSimulationTest.java @@ -4,7 +4,6 @@ import java.time.Duration; import java.util.*; -import java.util.concurrent.*; import java.util.stream.Stream; import org.junit.jupiter.api.DisplayName; @@ -21,12 +20,13 @@ class DinnerSimulationTest { static Stream testCases() { return Stream.of( - new TestCaseSettings("1-1-20", 1, 1, 20, 1, 3, 1, 3), - new TestCaseSettings("5-2-200", 5, 2, 200, 1, 3, 1, 3), - new TestCaseSettings("7-2-300", 7, 2, 300, 2, 5, 2, 5), - new TestCaseSettings("8-5-300", 8, 5, 300, 1, 2, 1, 2), - new TestCaseSettings("10-1-400", 10, 1, 400, 1, 2, 1, 2)// , - // new TestCaseSettings("7-2-10000", 7, 2, 100000, 1, 2, 1, 2) + new TestCaseSettings("1-1-20", 1, 1, 20, 1, 3, 1, 3), + new TestCaseSettings("5-2-200", 5, 2, 200, 1, 3, 1, 3), + new TestCaseSettings("7-2-300", 7, 2, 300, 2, 5, 2, 5), + new TestCaseSettings("8-5-300", 8, 5, 300, 1, 2, 1, 2), + new TestCaseSettings("10-1-400", 10, 1, 400, 1, 2, 1, 2), + new TestCaseSettings("30-1-3000", 30, 1, 1000, 1, 2, 1, 2), + new TestCaseSettings("7-2-10000", 7, 2, 10000, 1, 2, 1, 2) ); } @@ -35,7 +35,7 @@ static Stream testCases() { @ParameterizedTest(name = "{index} => {0}") @MethodSource("testCases") void finishesWithinTimeout(TestCaseSettings sc) { - assertTimeoutPreemptively(Duration.ofSeconds(240), () -> { + assertTimeoutPreemptively(Duration.ofSeconds(10), () -> { Result r = Main.startDinner(sc.toTaskStatic()); assertNotNull(r); }); @@ -45,7 +45,7 @@ void finishesWithinTimeout(TestCaseSettings sc) { @DisplayName("Ration counts correctly, no over/underusage") @ParameterizedTest(name = "{index} => {0}") @MethodSource("testCases") - void allRationsAccounted(TestCaseSettings sc) throws InterruptedException { + void allRationsEaten(TestCaseSettings sc) throws InterruptedException { Result r = Main.startDinner(sc.toTaskStatic()); long sumEaten = Arrays.stream(r.eatenByProgrammer()).sum(); long left = r.rationsLeft(); @@ -58,18 +58,21 @@ void allRationsAccounted(TestCaseSettings sc) throws InterruptedException { @DisplayName("Everyone eats almost the same amount of rations (contention-aware)") @ParameterizedTest(name = "{index} => {0}") @MethodSource("testCases") - void fairnessIsReasonable(TestCaseSettings sc) throws InterruptedException { + void fairOrNot(TestCaseSettings sc) throws InterruptedException { Result r = Main.startDinner(sc.toTaskStatic()); long[] arr = r.eatenByProgrammer().clone(); Arrays.sort(arr); long min = arr[0]; long max = arr[arr.length - 1]; - double sigma = Math.sqrt(sc.rations / (double) sc.programmers); - double allowedDelta = 4 * sigma; + double allowedDelta = Math.sqrt(sc.rations / (double) sc.programmers); assertTrue((max - min) <= allowedDelta, - String.format("Wide dispersion: min=%d, max=%d, mean≈%.2f, sigma≈%.2f, allowed≤%.2f", - min, max, sc.rations / (double) sc.programmers, sigma, allowedDelta)); + String.format("Wide dispersion: min=%d, max=%d, mean≈%.2f, allowed≤%.2f", + min, max, sc.rations / (double) sc.programmers, allowedDelta)); + +// assertTrue(false, +// String.format("Wide dispersion: min=%d, max=%d, mean≈%.2f, allowed≤%.2f", +// min, max, sc.rations / (double) sc.programmers, allowedDelta)); } }