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/README.md b/README.md
index d7a6ba3..e974d43 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+[](https://classroom.github.com/a/qcWcnElX)
# Java concurrency
# Цели и задачи л/р:
diff --git a/src/main/java/org/labs/Main.java b/src/main/java/org/labs/Main.java
index 9917247..82da43f 100644
--- a/src/main/java/org/labs/Main.java
+++ b/src/main/java/org/labs/Main.java
@@ -1,7 +1,98 @@
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!");
+ /**
+ * Точка входа в программу. Здесь задаются параметры выполняемыой программы (см. TaskStatic), вызывается выполнение программы,
+ * собираются результаты и выводятся в консоль
+ * @param args Не используется, так как статика задаётся хард-кодом внутри самого метода
+ * @throws InterruptedException Просто висит
+ */
+ 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());
}
+
+ /**
+ * Основной выполняющий метод, вызывается из мейна. Здесь задаются необходимые объекты, по типу ложек и шкафа с едой
+ * и происходит само выполнение программы, с досугом рационного возъедания и болтовнёй
+ * @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(), ts.getProgrammersCount());
+
+ int programmersCount = ts.getProgrammersCount();
+
+ LongAdder[] la = new LongAdder[programmersCount];
+ Arrays.setAll(la, _ -> new LongAdder());
+
+ Spoon[] s = new Spoon[programmersCount];
+ for (int i = 0; i < programmersCount; i++) {
+ s[i] = new Spoon(i);
+ }
+
+ try (ExecutorService programmersExec = Executors.newVirtualThreadPerTaskExecutor()) {
+ List programmersList = new ArrayList<>(programmersCount);
+
+ try {
+ for (int i = 0; i < programmersCount; i++) {
+ Spoon l = s[i];
+ Spoon r = s[(i + 1) % programmersCount];
+ 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();
+ }
+ }
+
+ long[] rationsEaten = new long[programmersCount];
+ long total = 0;
+ for (int i = 0; i < programmersCount; i++) {
+ rationsEaten[i] = la[i].sum();
+ total += rationsEaten[i];
+ }
+ long left = rs.getLeftRations().get();
+
+ 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
new file mode 100644
index 0000000..9d78d7f
--- /dev/null
+++ b/src/main/java/org/labs/Programmer.java
@@ -0,0 +1,179 @@
+package org.labs;
+
+import java.util.concurrent.ThreadLocalRandom;
+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 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;
+ }
+
+ /**
+ * Метод поедания еды программистом, здесь происходит периодическая болтовня, взятие ложек и поедание рационов
+ */
+ @Override
+ public void run() {
+ while (running) {
+ try {
+ talkAboutTeachers();
+ w.takeWaiter(stats);
+ try {
+ TwoSpoons sp = checkSpoons();
+ if (tryTakeSpoons(sp)) {
+ try {
+ if (!rs.takeRation()) {
+ running = false;
+ continue;
+ }
+ eatRation();
+ stats.increment();
+ } finally {
+ returnSpoons(sp);
+ }
+ }
+ } finally {
+ w.letWaiterGo();
+ }
+ } catch (InterruptedException ie) {
+ Thread.currentThread().interrupt();
+ break;
+ } catch (Throwable t) {
+ System.out.println("[programmer.run] throwable error - " + t.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Метод для взятия ложек, проверяем можем ли мы взять обе ложки в каком-то временном интервале.
+ * Если нет, то возвращаем обе ложки
+ * @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
+ */
+ 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);
+ } else {
+ return new TwoSpoons(rightSpoon, leftSpoon);
+ }
+ }
+
+ /**
+ * Метод выполнения каког-то действия - есть или болтать
+ * @param minMs Минимальное время выполнения действия
+ * @param maxMs Максимальное время выполнения действия
+ * @throws InterruptedException
+ */
+ 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 + ThreadLocalRandom.current().nextLong(span);
+ }
+
+ if (ms > 0) {
+ Thread.sleep(ms);
+ }
+ }
+
+ /**
+ * Возможность позвать свободного оффицианта и взять у него рацион
+ * @return Исход возможности взятия рациона
+ * @throws InterruptedException
+ */
+ private boolean takePortionWithWaiter() throws InterruptedException {
+ w.takeWaiter(stats);
+ try {
+ if (!rs.takeRation()) {
+ return false;
+ }
+ eatRation();
+ stats.increment();
+ return true;
+ } finally {
+ w.letWaiterGo();
+ }
+ }
+
+ /**
+ * Возврат обеих ложек
+ * @param sp Набор ложек конкретного программиста
+ */
+ private void returnSpoons(TwoSpoons sp) {
+ sp.second.put();
+ sp.first.put();
+ }
+
+ /**
+ * Стоп выполнения досуга поедания рациона
+ */
+ public void stop() {
+ running = 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/RationShelf.java b/src/main/java/org/labs/RationShelf.java
new file mode 100644
index 0000000..d71ebbd
--- /dev/null
+++ b/src/main/java/org/labs/RationShelf.java
@@ -0,0 +1,38 @@
+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);
+ }
+
+ /**
+ * Метод для взятия рациона, если ещё есть остатки
+ * @return Исход попытки взятия рациона
+ */
+ public boolean takeRation() {
+ while (true) {
+ long curCount = rationLeft.get();
+ if (curCount <= 0) {
+ return false;
+ }
+ if (rationLeft.compareAndSet(curCount, curCount - 1)) {
+ return true;
+ }
+ }
+ }
+
+ /**
+ * Получить число оставшихся рационов
+ * @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
new file mode 100644
index 0000000..80369a7
--- /dev/null
+++ b/src/main/java/org/labs/Spoon.java
@@ -0,0 +1,28 @@
+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 useWithTimeOut(long timeoutMillis) throws InterruptedException {
+ return lock.tryLock(timeoutMillis, TimeUnit.MILLISECONDS);
+ }
+
+ 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..c01e534
--- /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 = 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
new file mode 100644
index 0000000..73344dd
--- /dev/null
+++ b/src/main/java/org/labs/TwoSpoons.java
@@ -0,0 +1,14 @@
+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..575d617
--- /dev/null
+++ b/src/main/java/org/labs/Waiters.java
@@ -0,0 +1,78 @@
+package org.labs;
+
+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 AtomicInteger permits;
+ private final PriorityBlockingQueue queue;
+ private final ReentrantLock lock = new ReentrantLock();
+ private static final AtomicLong qOrder = new AtomicLong();
+
+ 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(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() {
+ 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
new file mode 100644
index 0000000..9f38789
--- /dev/null
+++ b/src/test/java/org/labs/DinnerSimulationTest.java
@@ -0,0 +1,78 @@
+package org.labs;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.time.Duration;
+import java.util.*;
+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("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)
+ );
+ }
+
+ @Order(1)
+ @DisplayName("Test ends in time without deadlocks")
+ @ParameterizedTest(name = "{index} => {0}")
+ @MethodSource("testCases")
+ void finishesWithinTimeout(TestCaseSettings sc) {
+ assertTimeoutPreemptively(Duration.ofSeconds(10), () -> {
+ Result r = Main.startDinner(sc.toTaskStatic());
+ assertNotNull(r);
+ });
+ }
+
+ @Order(2)
+ @DisplayName("Ration counts correctly, no over/underusage")
+ @ParameterizedTest(name = "{index} => {0}")
+ @MethodSource("testCases")
+ void allRationsEaten(TestCaseSettings sc) throws InterruptedException {
+ 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 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 allowedDelta = Math.sqrt(sc.rations / (double) sc.programmers);
+
+ assertTrue((max - min) <= 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));
+ }
+}
diff --git a/src/test/java/org/labs/TestCaseSettings.java b/src/test/java/org/labs/TestCaseSettings.java
new file mode 100644
index 0000000..99f5c31
--- /dev/null
+++ b/src/test/java/org/labs/TestCaseSettings.java
@@ -0,0 +1,39 @@
+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;
+ }
+
+ public 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