From 67b02d9bdf226ccc74b081f8a3b4cf1726771434 Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Thu, 18 Sep 2025 09:32:40 +0000 Subject: [PATCH 1/6] 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 ac1eab31ff500c0f034ff242a5996df305dd4de5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D0=BB=D0=B0=D0=B3=D0=BE=D1=80=D0=BE=D0=B4=D0=BD?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=9B=D1=8E=D0=B4=D0=BC=D0=B8=D0=BB=D0=B0?= Date: Sat, 20 Sep 2025 15:26:55 +0300 Subject: [PATCH 2/6] =?UTF-8?q?LAB-1=20=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D0=B7=D0=B0=D0=B4=D0=B0=D1=87?= =?UTF-8?q?=D0=B8=20=D0=BE=D0=B1=20=D0=BE=D0=B1=D0=B5=D0=B4=D0=B0=D1=8E?= =?UTF-8?q?=D1=89=D0=B8=D1=85=20=D0=BF=D1=80=D0=BE=D0=B3=D1=80=D0=B0=D0=BC?= =?UTF-8?q?=D0=BC=D0=B8=D1=81=D1=82=D0=B0=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 4 + src/main/java/org/labs/Main.java | 78 +++++++++++++++- src/main/java/org/labs/common/AsyncUtils.java | 29 ++++++ src/main/java/org/labs/common/MathUtils.java | 14 +++ src/main/java/org/labs/config/Config.java | 13 +++ .../labs/developer/model/DeveloperModel.java | 84 +++++++++++++++++ .../java/org/labs/fork/model/ForkModel.java | 14 +++ .../org/labs/kitchen/model/KitchenModel.java | 60 +++++++++++++ .../labs/serverequest/model/ServeRequest.java | 6 ++ src/main/java/org/labs/state/model/State.java | 11 +++ .../java/org/labs/state/model/StateModel.java | 89 +++++++++++++++++++ .../org/labs/waiter/model/WaiterModel.java | 37 ++++++++ 12 files changed, 438 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/labs/common/AsyncUtils.java create mode 100644 src/main/java/org/labs/common/MathUtils.java create mode 100644 src/main/java/org/labs/config/Config.java create mode 100644 src/main/java/org/labs/developer/model/DeveloperModel.java create mode 100644 src/main/java/org/labs/fork/model/ForkModel.java create mode 100644 src/main/java/org/labs/kitchen/model/KitchenModel.java create mode 100644 src/main/java/org/labs/serverequest/model/ServeRequest.java create mode 100644 src/main/java/org/labs/state/model/State.java create mode 100644 src/main/java/org/labs/state/model/StateModel.java create mode 100644 src/main/java/org/labs/waiter/model/WaiterModel.java diff --git a/build.gradle.kts b/build.gradle.kts index bda0d97..cbaf84a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,6 +10,10 @@ repositories { } dependencies { + implementation("org.slf4j:slf4j-api:2.0.16") + implementation("ch.qos.logback:logback-classic:1.5.13") + compileOnly("org.projectlombok:lombok:1.18.36") + annotationProcessor("org.projectlombok:lombok:1.18.36") testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") } diff --git a/src/main/java/org/labs/Main.java b/src/main/java/org/labs/Main.java index 9917247..516399c 100644 --- a/src/main/java/org/labs/Main.java +++ b/src/main/java/org/labs/Main.java @@ -1,7 +1,83 @@ package org.labs; +import lombok.extern.slf4j.Slf4j; +import org.labs.common.AsyncUtils; +import org.labs.common.MathUtils; +import org.labs.config.Config; +import org.labs.developer.model.DeveloperModel; +import org.labs.fork.model.ForkModel; +import org.labs.kitchen.model.KitchenModel; +import org.labs.state.model.StateModel; + +import java.util.Arrays; +import java.util.stream.IntStream; + +@Slf4j public class Main { + + private static final int TARGET_RESOURCE_COUNT = Config.DEVELOPER_COUNT; + private static final Thread[] THREADS = new Thread[TARGET_RESOURCE_COUNT]; + private static final int DISH_COUNT = Config.DISH_COUNT; + private static final int WAITER_COUNT = Config.WAITER_COUNT; + public static void main(String[] args) { - System.out.println("Hello, World!"); + var startDate = System.currentTimeMillis(); + log.info("Время {} mc. Обед начался", startDate); + + var forks = IntStream.range(0, TARGET_RESOURCE_COUNT) + .mapToObj(ForkModel::new) + .toArray(ForkModel[]::new); + + var developers = new DeveloperModel[TARGET_RESOURCE_COUNT]; + var state = new StateModel(developers); + var kitchen = new KitchenModel(DISH_COUNT, WAITER_COUNT); + + IntStream.range(0, TARGET_RESOURCE_COUNT).forEach(number -> { + var leftFork = forks[number]; + var rightFork = forks[(number + 1) % forks.length]; + + developers[number] = new DeveloperModel(number, leftFork, rightFork, state, kitchen); + + THREADS[number] = new Thread(developers[number], "developer-" + (number + 1)); + THREADS[number].start(); + }); + + while (kitchen.getRemainingDishCount() > 0) { + AsyncUtils.waitMillis(200); + log.debug("Оставшееся количество блюд на кухне {}", kitchen.getRemainingDishCount()); + } + + AsyncUtils.waitMillis(Config.DINNER_DURATION_IN_MS); + stopDevelopers(developers); + kitchen.stopWaiters(); + AsyncUtils.stopThreads(THREADS); + + log.info("Время выполнения: {} mc. Обед завершен", System.currentTimeMillis() - startDate); + printStates(developers); + } + + private static void stopDevelopers(DeveloperModel[] developers) { + Arrays.stream(developers) + .filter(d -> !d.getIsStopped().get()) + .forEach(DeveloperModel::stop); + + AsyncUtils.waitMillis(200); + } + + private static void printStates(DeveloperModel[] developers) { + int totalCount = Arrays.stream(developers) + .mapToInt(developer -> developer.getEatCount().intValue()) + .sum(); + + if (totalCount > 0) { + log.info("Итоговое состояние:"); + log.info("Всего съедено: {}", totalCount); + + IntStream.range(0, developers.length) + .forEachOrdered(i -> { + double value = 100.0 * developers[i].getEatCount().intValue() / totalCount; + log.info("Разработчик {} съел {}% блюд", i + 1, MathUtils.roundTo2Digits(value)); + }); + } } } \ No newline at end of file diff --git a/src/main/java/org/labs/common/AsyncUtils.java b/src/main/java/org/labs/common/AsyncUtils.java new file mode 100644 index 0000000..afe51da --- /dev/null +++ b/src/main/java/org/labs/common/AsyncUtils.java @@ -0,0 +1,29 @@ +package org.labs.common; + +import lombok.extern.slf4j.Slf4j; + +import java.util.Arrays; + +@Slf4j +public class AsyncUtils { + + public static void waitMillis(long millis) { + if (millis <= 0) { + return; + } + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + log.warn("Поток {} был прерван", Thread.currentThread().getName()); + Thread.currentThread().interrupt(); + } + } + + public static void stopThreads(Thread[] threads) { + Arrays.stream(threads) + .filter(t -> !t.isInterrupted()) + .forEach(Thread::interrupt); + + waitMillis(100); + } +} diff --git a/src/main/java/org/labs/common/MathUtils.java b/src/main/java/org/labs/common/MathUtils.java new file mode 100644 index 0000000..b59564a --- /dev/null +++ b/src/main/java/org/labs/common/MathUtils.java @@ -0,0 +1,14 @@ +package org.labs.common; + +import org.labs.config.Config; + +public class MathUtils { + + public static double roundTo2Digits(double value) { + return Math.round(value * 100) / 100.0; + } + + public static int getRandomInt() { + return (int) (Math.random() * Config.MAX_WAIT_MS); + } +} diff --git a/src/main/java/org/labs/config/Config.java b/src/main/java/org/labs/config/Config.java new file mode 100644 index 0000000..3ada458 --- /dev/null +++ b/src/main/java/org/labs/config/Config.java @@ -0,0 +1,13 @@ +package org.labs.config; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class Config { + public static int DEVELOPER_COUNT = 7; + public static int MAX_WAIT_MS = 1; + public static int DINNER_DURATION_IN_MS = 1000; + public static int DISH_COUNT = 1000; + public static int WAITER_COUNT = 2; +} diff --git a/src/main/java/org/labs/developer/model/DeveloperModel.java b/src/main/java/org/labs/developer/model/DeveloperModel.java new file mode 100644 index 0000000..6558e87 --- /dev/null +++ b/src/main/java/org/labs/developer/model/DeveloperModel.java @@ -0,0 +1,84 @@ +package org.labs.developer.model; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.labs.common.MathUtils; +import org.labs.fork.model.ForkModel; +import org.labs.kitchen.model.KitchenModel; +import org.labs.serverequest.model.ServeRequest; +import org.labs.state.model.StateModel; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +@Slf4j +@Getter +@RequiredArgsConstructor +public class DeveloperModel implements Runnable { + + private final int id; + private final ForkModel leftFork; + private final ForkModel rightFork; + private final StateModel state; + private final KitchenModel kitchen; + + public final AtomicBoolean isStopped = new AtomicBoolean(); + + public AtomicInteger eatCount = new AtomicInteger(); + + @Override + public void run() { + try { + while (!isStopped.get()) { + think(); + boolean served = callWaiter(); + if (!served) break; + + state.takeForks(id, leftFork, rightFork); + eat(); + state.putForks(id, leftFork, rightFork); + } + } catch (InterruptedException ignored) { + log.warn("Поток {} был прерван", Thread.currentThread().getName()); + } + } + + public void stop() { + isStopped.set(true); + } + + private void think() throws InterruptedException { + log.debug("Время: {} ms. Разработчик начал обсуждать преподавателей", System.currentTimeMillis()); + Thread.sleep(MathUtils.getRandomInt()); + } + + private void eat() throws InterruptedException { + log.debug("Время: {} ms. Разработчик начал есть", System.currentTimeMillis()); + Thread.sleep(MathUtils.getRandomInt()); + eatCount.incrementAndGet(); + } + + private boolean callWaiter() throws InterruptedException { + CompletableFuture servedFuture = new CompletableFuture<>(); + var serveRequest = new ServeRequest(id, servedFuture); + kitchen.submitRequest(serveRequest); + + boolean isServed; + try { + isServed = servedFuture.get(); + } catch (ExecutionException e) { + log.warn("Разработчик {} не смог вызвать официанта", id + 1, e); + return false; + } + + if (!isServed) { + log.info("Разработчик {} не может получить новую порцию. Прием пищи прекращен", id + 1); + isStopped.set(true); + return false; + } + return true; + } +} diff --git a/src/main/java/org/labs/fork/model/ForkModel.java b/src/main/java/org/labs/fork/model/ForkModel.java new file mode 100644 index 0000000..873fc9d --- /dev/null +++ b/src/main/java/org/labs/fork/model/ForkModel.java @@ -0,0 +1,14 @@ +package org.labs.fork.model; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + + +@Getter +@RequiredArgsConstructor +public class ForkModel { + private final int id; + @Setter + private boolean isAvailable = true; +} diff --git a/src/main/java/org/labs/kitchen/model/KitchenModel.java b/src/main/java/org/labs/kitchen/model/KitchenModel.java new file mode 100644 index 0000000..e6c29d2 --- /dev/null +++ b/src/main/java/org/labs/kitchen/model/KitchenModel.java @@ -0,0 +1,60 @@ +package org.labs.kitchen.model; + +import org.labs.serverequest.model.ServeRequest; +import org.labs.waiter.model.WaiterModel; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.IntStream; + +public class KitchenModel { + + private final AtomicInteger remainingDishCount; + private final BlockingQueue requestQueue = new LinkedBlockingQueue<>(); + private final List waiterThreads = new ArrayList<>(); + + public KitchenModel(int initialDishes, int waiterCount) { + this.remainingDishCount = new AtomicInteger(initialDishes); + + IntStream.range(0, waiterCount) + .mapToObj(number -> new Thread(new WaiterModel(number, this), "waiter-" + (number + 1))) + .peek(Thread::start) + .forEach(waiterThreads::add); + } + + public void submitRequest(ServeRequest req) throws InterruptedException { + requestQueue.put(req); + } + + public ServeRequest takeRequest() throws InterruptedException { + return requestQueue.take(); + } + + public boolean takeDishIfAvailable() { + int currentRemainingDishCount; + do { + currentRemainingDishCount = remainingDishCount.get(); + if (currentRemainingDishCount <= 0) return false; + } while (!remainingDishCount.compareAndSet(currentRemainingDishCount, currentRemainingDishCount - 1)); + return true; + } + + public int getRemainingDishCount() { + return remainingDishCount.get(); + } + + public boolean isDepleted() { + return remainingDishCount.get() <= 0; + } + + public boolean isRequestQueueEmpty() { + return requestQueue.isEmpty(); + } + + public void stopWaiters() { + for (Thread waiterThread : waiterThreads) waiterThread.interrupt(); + } +} diff --git a/src/main/java/org/labs/serverequest/model/ServeRequest.java b/src/main/java/org/labs/serverequest/model/ServeRequest.java new file mode 100644 index 0000000..3c23f09 --- /dev/null +++ b/src/main/java/org/labs/serverequest/model/ServeRequest.java @@ -0,0 +1,6 @@ +package org.labs.serverequest.model; + +import java.util.concurrent.CompletableFuture; + +public record ServeRequest(int developerId, CompletableFuture served) { +} diff --git a/src/main/java/org/labs/state/model/State.java b/src/main/java/org/labs/state/model/State.java new file mode 100644 index 0000000..5282c02 --- /dev/null +++ b/src/main/java/org/labs/state/model/State.java @@ -0,0 +1,11 @@ +package org.labs.state.model; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public enum State { + HUNGRY, + EATING, + DISCUSS_TEACHERS +} diff --git a/src/main/java/org/labs/state/model/StateModel.java b/src/main/java/org/labs/state/model/StateModel.java new file mode 100644 index 0000000..9128e7c --- /dev/null +++ b/src/main/java/org/labs/state/model/StateModel.java @@ -0,0 +1,89 @@ +package org.labs.state.model; + +import lombok.extern.slf4j.Slf4j; +import org.labs.developer.model.DeveloperModel; +import org.labs.fork.model.ForkModel; + +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +@Slf4j +public class StateModel { + + private final int developerCount; + private final DeveloperModel[] developers; + private final State[] state; + private final Lock lock; + private final Condition[] conditions; + + public StateModel(DeveloperModel[] developers) { + this.developers = developers; + this.developerCount = developers.length; + lock = new ReentrantLock(); + state = new State[developerCount]; + conditions = new Condition[developerCount]; + IntStream.range(0, developerCount).forEach(i -> { + state[i] = State.DISCUSS_TEACHERS; + conditions[i] = lock.newCondition(); + }); + } + + public void takeForks(int id, ForkModel leftFork, ForkModel rightFork) { + lock.lock(); + try { + updateDeveloperState(id, State.HUNGRY); + + while (!developers[id].getIsStopped().get() && (!leftFork.isAvailable() || !rightFork.isAvailable())) { + conditions[id].await(); + } + + leftFork.setAvailable(false); + rightFork.setAvailable(false); + + updateDeveloperState(id, State.EATING); + + printState(); + } catch (InterruptedException e) { + log.warn("Поток {} был прерван", Thread.currentThread().getName()); + } finally { + lock.unlock(); + } + } + + + public void putForks(int developerId, ForkModel leftFork, ForkModel rightFork) { + lock.lock(); + try { + updateDeveloperState(developerId, State.DISCUSS_TEACHERS); + + leftFork.setAvailable(true); + rightFork.setAvailable(true); + + conditions[(developerId + 1) % developerCount].signalAll(); + conditions[(developerId + developerCount - 1) % developerCount].signalAll(); + + printState(); + } finally { + lock.unlock(); + } + } + + private void updateDeveloperState(int id, State state) { + this.state[id] = state; + } + + private void printState() { + String result = IntStream.range(0, developerCount) + .mapToObj(i -> switch (state[i]) { + case DISCUSS_TEACHERS -> "обсуждает преподавателей"; + case HUNGRY -> "голоден"; + case EATING -> "ест"; + }) + .collect(Collectors.joining(" ", "Сводка по занятости разработчиков: ", "")); + + log.debug("{}", result); + } +} diff --git a/src/main/java/org/labs/waiter/model/WaiterModel.java b/src/main/java/org/labs/waiter/model/WaiterModel.java new file mode 100644 index 0000000..009e5ec --- /dev/null +++ b/src/main/java/org/labs/waiter/model/WaiterModel.java @@ -0,0 +1,37 @@ +package org.labs.waiter.model; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.labs.kitchen.model.KitchenModel; + +@Slf4j +@RequiredArgsConstructor +public class WaiterModel implements Runnable { + + private final int id; + private final KitchenModel kitchen; + + @Override + public void run() { + try { + while (true) { + var serveRequest = kitchen.takeRequest(); + if (serveRequest == null) { + continue; + } + + var isServed = kitchen.takeDishIfAvailable(); + log.debug("Официант {} начал обслуживание разработчика {} -> {}", id + 1, serveRequest.developerId() + 1, isServed); + serveRequest.served().complete(isServed); + + if (!isServed && kitchen.isDepleted() && kitchen.isRequestQueueEmpty()) { + log.info("Официант {} разнес все блюда, заказы закончились", id + 1); + break; + } + } + } catch (InterruptedException e) { + log.warn("Поток {} был прерван", Thread.currentThread().getName()); + Thread.currentThread().interrupt(); + } + } +} From 37919d8f70dbc07c8370ea616c88662266d63ddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D0=BB=D0=B0=D0=B3=D0=BE=D1=80=D0=BE=D0=B4=D0=BD?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=9B=D1=8E=D0=B4=D0=BC=D0=B8=D0=BB=D0=B0?= Date: Sat, 20 Sep 2025 16:17:45 +0300 Subject: [PATCH 3/6] =?UTF-8?q?LAB-1=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20logback.xml,=20=D0=B0=D0=BA?= =?UTF-8?q?=D1=82=D1=83=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BD=D1=84=D0=B8=D0=B3=D1=83=D1=80=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D0=BE=D0=BD=D0=BD=D1=8B=D1=85=20=D0=BF=D0=B0=D1=80=D0=B0?= =?UTF-8?q?=D0=BC=D0=B5=D1=82=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/org/labs/config/Config.java | 4 ++-- src/main/resources/logback.xml | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 src/main/resources/logback.xml diff --git a/src/main/java/org/labs/config/Config.java b/src/main/java/org/labs/config/Config.java index 3ada458..fc18f38 100644 --- a/src/main/java/org/labs/config/Config.java +++ b/src/main/java/org/labs/config/Config.java @@ -7,7 +7,7 @@ public class Config { public static int DEVELOPER_COUNT = 7; public static int MAX_WAIT_MS = 1; - public static int DINNER_DURATION_IN_MS = 1000; - public static int DISH_COUNT = 1000; + public static int DINNER_DURATION_IN_MS = 10; + public static int DISH_COUNT = 1000000; public static int WAITER_COUNT = 2; } diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..3526d48 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,12 @@ + + + + + [%d{HH:mm:ss}] [%thread] %-5level %msg%n + + + + + + + \ No newline at end of file From 2c3781cd8b26ec2fa6eb464dbe9d4b17d073f59e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D0=BB=D0=B0=D0=B3=D0=BE=D1=80=D0=BE=D0=B4=D0=BD?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=9B=D1=8E=D0=B4=D0=BC=D0=B8=D0=BB=D0=B0?= Date: Sun, 21 Sep 2025 22:10:10 +0300 Subject: [PATCH 4/6] =?UTF-8?q?LAB-1=20=D0=A0=D0=B5=D1=84=D0=B0=D0=BA?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=B8=D0=BD=D0=B3:=20=D1=80=D0=B0=D0=B7?= =?UTF-8?q?=D0=B4=D0=B5=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B7=D0=BE=D0=BD?= =?UTF-8?q?=20=D0=BE=D1=82=D0=B2=D0=B5=D1=82=D1=81=D1=82=D0=B2=D0=B5=D0=BD?= =?UTF-8?q?=D0=BD=D0=BE=D1=81=D1=82=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/labs/DinnerProcessingService.java | 97 +++++++++++++++++++ src/main/java/org/labs/Main.java | 55 +---------- .../labs/developer/model/DeveloperModel.java | 33 +++---- .../org/labs/kitchen/model/KitchenModel.java | 19 +--- .../labs/serverequest/model/ServeRequest.java | 9 +- .../org/labs/waiter/model/WaiterModel.java | 4 +- 6 files changed, 127 insertions(+), 90 deletions(-) create mode 100644 src/main/java/org/labs/DinnerProcessingService.java diff --git a/src/main/java/org/labs/DinnerProcessingService.java b/src/main/java/org/labs/DinnerProcessingService.java new file mode 100644 index 0000000..4925572 --- /dev/null +++ b/src/main/java/org/labs/DinnerProcessingService.java @@ -0,0 +1,97 @@ +package org.labs; + +import lombok.extern.slf4j.Slf4j; +import org.labs.common.AsyncUtils; +import org.labs.config.Config; +import org.labs.developer.model.DeveloperModel; +import org.labs.fork.model.ForkModel; +import org.labs.kitchen.model.KitchenModel; +import org.labs.state.model.StateModel; +import org.labs.waiter.model.WaiterModel; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.IntStream; + +@Slf4j +public class DinnerProcessingService { + + private static final int TARGET_RESOURCE_COUNT = Config.DEVELOPER_COUNT; + private static final Thread[] DEVELOPER_THREADS = new Thread[TARGET_RESOURCE_COUNT]; + private static final int DISH_COUNT = Config.DISH_COUNT; + private static final int WAITER_COUNT = Config.WAITER_COUNT; + private static final Thread[] WAITER_THREADS = new Thread[WAITER_COUNT]; + + public DeveloperModel[] runDinner() { + var startDate = System.currentTimeMillis(); + log.info("Время {} мс. Обед начался", startDate); + + var kitchen = new KitchenModel(new AtomicInteger(DISH_COUNT)); + var forks = createForks(); + var developers = createDevelopers(forks, kitchen); + var waiters = createWaiters(kitchen); + + startDeveloperThreads(developers); + startWaiterThreads(waiters); + + monitorDinner(kitchen); + + stopDinner(); + + log.info("Время выполнения: {} мс. Обед завершен", System.currentTimeMillis() - startDate); + + return developers; + } + + private ForkModel[] createForks() { + return IntStream.range(0, TARGET_RESOURCE_COUNT) + .mapToObj(ForkModel::new) + .toArray(ForkModel[]::new); + } + + private DeveloperModel[] createDevelopers(ForkModel[] forks, KitchenModel kitchen) { + var developers = new DeveloperModel[TARGET_RESOURCE_COUNT]; + var state = new StateModel(developers); + + IntStream.range(0, developers.length).forEach(number -> { + var leftFork = forks[number]; + var rightFork = forks[(number + 1) % forks.length]; + developers[number] = new DeveloperModel(number, leftFork, rightFork, state, kitchen); + }); + return developers; + } + + private WaiterModel[] createWaiters(KitchenModel kitchen) { + var waiters = new WaiterModel[WAITER_COUNT]; + IntStream.range(0, WAITER_COUNT).forEach(number -> { + waiters[number] = new WaiterModel(number, kitchen); + }); + return waiters; + } + + private void startWaiterThreads(WaiterModel[] waiters) { + IntStream.range(0, waiters.length).forEach(number -> { + WAITER_THREADS[number] = new Thread(waiters[number], "waiter-" + (number + 1)); + WAITER_THREADS[number].start(); + }); + } + + private void startDeveloperThreads(DeveloperModel[] developers) { + IntStream.range(0, developers.length).forEach(number -> { + DEVELOPER_THREADS[number] = new Thread(developers[number], "developer-" + (number + 1)); + DEVELOPER_THREADS[number].start(); + }); + } + + private void monitorDinner(KitchenModel kitchen) { + while (kitchen.getRemainingDishCount() > 0) { + AsyncUtils.waitMillis(200); + log.debug("Оставшееся количество блюд на кухне {}", kitchen.getRemainingDishCount()); + } + } + + private void stopDinner() { + AsyncUtils.waitMillis(Config.DINNER_DURATION_IN_MS); + AsyncUtils.stopThreads(WAITER_THREADS); + AsyncUtils.stopThreads(DEVELOPER_THREADS); + } +} diff --git a/src/main/java/org/labs/Main.java b/src/main/java/org/labs/Main.java index 516399c..949c99e 100644 --- a/src/main/java/org/labs/Main.java +++ b/src/main/java/org/labs/Main.java @@ -1,69 +1,24 @@ package org.labs; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.labs.common.AsyncUtils; import org.labs.common.MathUtils; -import org.labs.config.Config; import org.labs.developer.model.DeveloperModel; -import org.labs.fork.model.ForkModel; -import org.labs.kitchen.model.KitchenModel; -import org.labs.state.model.StateModel; import java.util.Arrays; import java.util.stream.IntStream; @Slf4j +@RequiredArgsConstructor public class Main { - private static final int TARGET_RESOURCE_COUNT = Config.DEVELOPER_COUNT; - private static final Thread[] THREADS = new Thread[TARGET_RESOURCE_COUNT]; - private static final int DISH_COUNT = Config.DISH_COUNT; - private static final int WAITER_COUNT = Config.WAITER_COUNT; + private static final DinnerProcessingService dinnerProcessingService = new DinnerProcessingService(); public static void main(String[] args) { - var startDate = System.currentTimeMillis(); - log.info("Время {} mc. Обед начался", startDate); - - var forks = IntStream.range(0, TARGET_RESOURCE_COUNT) - .mapToObj(ForkModel::new) - .toArray(ForkModel[]::new); - - var developers = new DeveloperModel[TARGET_RESOURCE_COUNT]; - var state = new StateModel(developers); - var kitchen = new KitchenModel(DISH_COUNT, WAITER_COUNT); - - IntStream.range(0, TARGET_RESOURCE_COUNT).forEach(number -> { - var leftFork = forks[number]; - var rightFork = forks[(number + 1) % forks.length]; - - developers[number] = new DeveloperModel(number, leftFork, rightFork, state, kitchen); - - THREADS[number] = new Thread(developers[number], "developer-" + (number + 1)); - THREADS[number].start(); - }); - - while (kitchen.getRemainingDishCount() > 0) { - AsyncUtils.waitMillis(200); - log.debug("Оставшееся количество блюд на кухне {}", kitchen.getRemainingDishCount()); - } - - AsyncUtils.waitMillis(Config.DINNER_DURATION_IN_MS); - stopDevelopers(developers); - kitchen.stopWaiters(); - AsyncUtils.stopThreads(THREADS); - - log.info("Время выполнения: {} mc. Обед завершен", System.currentTimeMillis() - startDate); + var developers = dinnerProcessingService.runDinner(); printStates(developers); } - private static void stopDevelopers(DeveloperModel[] developers) { - Arrays.stream(developers) - .filter(d -> !d.getIsStopped().get()) - .forEach(DeveloperModel::stop); - - AsyncUtils.waitMillis(200); - } - private static void printStates(DeveloperModel[] developers) { int totalCount = Arrays.stream(developers) .mapToInt(developer -> developer.getEatCount().intValue()) @@ -75,7 +30,7 @@ private static void printStates(DeveloperModel[] developers) { IntStream.range(0, developers.length) .forEachOrdered(i -> { - double value = 100.0 * developers[i].getEatCount().intValue() / totalCount; + var value = 100.0 * developers[i].getEatCount().intValue() / totalCount; log.info("Разработчик {} съел {}% блюд", i + 1, MathUtils.roundTo2Digits(value)); }); } diff --git a/src/main/java/org/labs/developer/model/DeveloperModel.java b/src/main/java/org/labs/developer/model/DeveloperModel.java index 6558e87..edd7507 100644 --- a/src/main/java/org/labs/developer/model/DeveloperModel.java +++ b/src/main/java/org/labs/developer/model/DeveloperModel.java @@ -9,7 +9,6 @@ import org.labs.serverequest.model.ServeRequest; import org.labs.state.model.StateModel; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -34,8 +33,11 @@ public void run() { try { while (!isStopped.get()) { think(); - boolean served = callWaiter(); - if (!served) break; + var served = placeOrder(); + if (!served) { + isStopped.set(true); + break; + } state.takeForks(id, leftFork, rightFork); eat(); @@ -46,10 +48,6 @@ public void run() { } } - public void stop() { - isStopped.set(true); - } - private void think() throws InterruptedException { log.debug("Время: {} ms. Разработчик начал обсуждать преподавателей", System.currentTimeMillis()); Thread.sleep(MathUtils.getRandomInt()); @@ -61,24 +59,19 @@ private void eat() throws InterruptedException { eatCount.incrementAndGet(); } - private boolean callWaiter() throws InterruptedException { - CompletableFuture servedFuture = new CompletableFuture<>(); - var serveRequest = new ServeRequest(id, servedFuture); - kitchen.submitRequest(serveRequest); - - boolean isServed; + private boolean placeOrder() throws InterruptedException { try { - isServed = servedFuture.get(); + var serveRequest = new ServeRequest(id); + kitchen.submitRequest(serveRequest); + var isServed = serveRequest.getServed().get(); + if (!isServed) { + log.info("Разработчик {} не может получить новую порцию. Его обед завершен", id + 1); + return false; + } } catch (ExecutionException e) { log.warn("Разработчик {} не смог вызвать официанта", id + 1, e); return false; } - - if (!isServed) { - log.info("Разработчик {} не может получить новую порцию. Прием пищи прекращен", id + 1); - isStopped.set(true); - return false; - } return true; } } diff --git a/src/main/java/org/labs/kitchen/model/KitchenModel.java b/src/main/java/org/labs/kitchen/model/KitchenModel.java index e6c29d2..d972fca 100644 --- a/src/main/java/org/labs/kitchen/model/KitchenModel.java +++ b/src/main/java/org/labs/kitchen/model/KitchenModel.java @@ -1,29 +1,17 @@ package org.labs.kitchen.model; +import lombok.RequiredArgsConstructor; import org.labs.serverequest.model.ServeRequest; -import org.labs.waiter.model.WaiterModel; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.IntStream; +@RequiredArgsConstructor public class KitchenModel { private final AtomicInteger remainingDishCount; private final BlockingQueue requestQueue = new LinkedBlockingQueue<>(); - private final List waiterThreads = new ArrayList<>(); - - public KitchenModel(int initialDishes, int waiterCount) { - this.remainingDishCount = new AtomicInteger(initialDishes); - - IntStream.range(0, waiterCount) - .mapToObj(number -> new Thread(new WaiterModel(number, this), "waiter-" + (number + 1))) - .peek(Thread::start) - .forEach(waiterThreads::add); - } public void submitRequest(ServeRequest req) throws InterruptedException { requestQueue.put(req); @@ -54,7 +42,4 @@ public boolean isRequestQueueEmpty() { return requestQueue.isEmpty(); } - public void stopWaiters() { - for (Thread waiterThread : waiterThreads) waiterThread.interrupt(); - } } diff --git a/src/main/java/org/labs/serverequest/model/ServeRequest.java b/src/main/java/org/labs/serverequest/model/ServeRequest.java index 3c23f09..ccd9009 100644 --- a/src/main/java/org/labs/serverequest/model/ServeRequest.java +++ b/src/main/java/org/labs/serverequest/model/ServeRequest.java @@ -1,6 +1,13 @@ package org.labs.serverequest.model; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + import java.util.concurrent.CompletableFuture; -public record ServeRequest(int developerId, CompletableFuture served) { +@Getter +@RequiredArgsConstructor +public class ServeRequest { + private final int developerId; + private final CompletableFuture served = new CompletableFuture<>(); } diff --git a/src/main/java/org/labs/waiter/model/WaiterModel.java b/src/main/java/org/labs/waiter/model/WaiterModel.java index 009e5ec..6f6f49a 100644 --- a/src/main/java/org/labs/waiter/model/WaiterModel.java +++ b/src/main/java/org/labs/waiter/model/WaiterModel.java @@ -21,8 +21,8 @@ public void run() { } var isServed = kitchen.takeDishIfAvailable(); - log.debug("Официант {} начал обслуживание разработчика {} -> {}", id + 1, serveRequest.developerId() + 1, isServed); - serveRequest.served().complete(isServed); + log.debug("Официант {} начал обслуживание разработчика {} -> {}", id + 1, serveRequest.getDeveloperId() + 1, isServed); + serveRequest.getServed().complete(isServed); if (!isServed && kitchen.isDepleted() && kitchen.isRequestQueueEmpty()) { log.info("Официант {} разнес все блюда, заказы закончились", id + 1); From 11d614bdeb24b76e26641a63ca88705ebc69847e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D0=BB=D0=B0=D0=B3=D0=BE=D1=80=D0=BE=D0=B4=D0=BD?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=9B=D1=8E=D0=B4=D0=BC=D0=B8=D0=BB=D0=B0?= Date: Mon, 22 Sep 2025 00:23:26 +0300 Subject: [PATCH 5/6] =?UTF-8?q?LAB-1=20=D0=A0=D0=B5=D0=BD=D0=B5=D0=B9?= =?UTF-8?q?=D0=BC=D0=B8=D0=BD=D0=B3=20=D0=BE=D0=B1=D1=8A=D0=B5=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/labs/DinnerProcessingService.java | 20 +++++++++---------- .../labs/developer/model/DeveloperModel.java | 10 +++++----- .../model/SpoonModel.java} | 4 ++-- .../java/org/labs/state/model/StateModel.java | 16 +++++++-------- 4 files changed, 25 insertions(+), 25 deletions(-) rename src/main/java/org/labs/{fork/model/ForkModel.java => spoon/model/SpoonModel.java} (78%) diff --git a/src/main/java/org/labs/DinnerProcessingService.java b/src/main/java/org/labs/DinnerProcessingService.java index 4925572..4349223 100644 --- a/src/main/java/org/labs/DinnerProcessingService.java +++ b/src/main/java/org/labs/DinnerProcessingService.java @@ -4,7 +4,7 @@ import org.labs.common.AsyncUtils; import org.labs.config.Config; import org.labs.developer.model.DeveloperModel; -import org.labs.fork.model.ForkModel; +import org.labs.spoon.model.SpoonModel; import org.labs.kitchen.model.KitchenModel; import org.labs.state.model.StateModel; import org.labs.waiter.model.WaiterModel; @@ -26,8 +26,8 @@ public DeveloperModel[] runDinner() { log.info("Время {} мс. Обед начался", startDate); var kitchen = new KitchenModel(new AtomicInteger(DISH_COUNT)); - var forks = createForks(); - var developers = createDevelopers(forks, kitchen); + var spoons = createSpoons(); + var developers = createDevelopers(spoons, kitchen); var waiters = createWaiters(kitchen); startDeveloperThreads(developers); @@ -42,20 +42,20 @@ public DeveloperModel[] runDinner() { return developers; } - private ForkModel[] createForks() { + private SpoonModel[] createSpoons() { return IntStream.range(0, TARGET_RESOURCE_COUNT) - .mapToObj(ForkModel::new) - .toArray(ForkModel[]::new); + .mapToObj(SpoonModel::new) + .toArray(SpoonModel[]::new); } - private DeveloperModel[] createDevelopers(ForkModel[] forks, KitchenModel kitchen) { + private DeveloperModel[] createDevelopers(SpoonModel[] spoons, KitchenModel kitchen) { var developers = new DeveloperModel[TARGET_RESOURCE_COUNT]; var state = new StateModel(developers); IntStream.range(0, developers.length).forEach(number -> { - var leftFork = forks[number]; - var rightFork = forks[(number + 1) % forks.length]; - developers[number] = new DeveloperModel(number, leftFork, rightFork, state, kitchen); + var leftSpoon = spoons[number]; + var rightSpoon = spoons[(number + 1) % spoons.length]; + developers[number] = new DeveloperModel(number, leftSpoon, rightSpoon, state, kitchen); }); return developers; } diff --git a/src/main/java/org/labs/developer/model/DeveloperModel.java b/src/main/java/org/labs/developer/model/DeveloperModel.java index edd7507..e9540e0 100644 --- a/src/main/java/org/labs/developer/model/DeveloperModel.java +++ b/src/main/java/org/labs/developer/model/DeveloperModel.java @@ -4,7 +4,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.labs.common.MathUtils; -import org.labs.fork.model.ForkModel; +import org.labs.spoon.model.SpoonModel; import org.labs.kitchen.model.KitchenModel; import org.labs.serverequest.model.ServeRequest; import org.labs.state.model.StateModel; @@ -19,8 +19,8 @@ public class DeveloperModel implements Runnable { private final int id; - private final ForkModel leftFork; - private final ForkModel rightFork; + private final SpoonModel leftSpoon; + private final SpoonModel rightSpoon; private final StateModel state; private final KitchenModel kitchen; @@ -39,9 +39,9 @@ public void run() { break; } - state.takeForks(id, leftFork, rightFork); + state.takeSpoons(id, leftSpoon, rightSpoon); eat(); - state.putForks(id, leftFork, rightFork); + state.putSpoons(id, leftSpoon, rightSpoon); } } catch (InterruptedException ignored) { log.warn("Поток {} был прерван", Thread.currentThread().getName()); diff --git a/src/main/java/org/labs/fork/model/ForkModel.java b/src/main/java/org/labs/spoon/model/SpoonModel.java similarity index 78% rename from src/main/java/org/labs/fork/model/ForkModel.java rename to src/main/java/org/labs/spoon/model/SpoonModel.java index 873fc9d..dde217c 100644 --- a/src/main/java/org/labs/fork/model/ForkModel.java +++ b/src/main/java/org/labs/spoon/model/SpoonModel.java @@ -1,4 +1,4 @@ -package org.labs.fork.model; +package org.labs.spoon.model; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -7,7 +7,7 @@ @Getter @RequiredArgsConstructor -public class ForkModel { +public class SpoonModel { private final int id; @Setter private boolean isAvailable = true; diff --git a/src/main/java/org/labs/state/model/StateModel.java b/src/main/java/org/labs/state/model/StateModel.java index 9128e7c..7501794 100644 --- a/src/main/java/org/labs/state/model/StateModel.java +++ b/src/main/java/org/labs/state/model/StateModel.java @@ -2,7 +2,7 @@ import lombok.extern.slf4j.Slf4j; import org.labs.developer.model.DeveloperModel; -import org.labs.fork.model.ForkModel; +import org.labs.spoon.model.SpoonModel; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; @@ -31,17 +31,17 @@ public StateModel(DeveloperModel[] developers) { }); } - public void takeForks(int id, ForkModel leftFork, ForkModel rightFork) { + public void takeSpoons(int id, SpoonModel leftSpoon, SpoonModel rightSpoon) { lock.lock(); try { updateDeveloperState(id, State.HUNGRY); - while (!developers[id].getIsStopped().get() && (!leftFork.isAvailable() || !rightFork.isAvailable())) { + while (!developers[id].getIsStopped().get() && (!leftSpoon.isAvailable() || !rightSpoon.isAvailable())) { conditions[id].await(); } - leftFork.setAvailable(false); - rightFork.setAvailable(false); + leftSpoon.setAvailable(false); + rightSpoon.setAvailable(false); updateDeveloperState(id, State.EATING); @@ -54,13 +54,13 @@ public void takeForks(int id, ForkModel leftFork, ForkModel rightFork) { } - public void putForks(int developerId, ForkModel leftFork, ForkModel rightFork) { + public void putSpoons(int developerId, SpoonModel leftSpoon, SpoonModel rightSpoon) { lock.lock(); try { updateDeveloperState(developerId, State.DISCUSS_TEACHERS); - leftFork.setAvailable(true); - rightFork.setAvailable(true); + leftSpoon.setAvailable(true); + rightSpoon.setAvailable(true); conditions[(developerId + 1) % developerCount].signalAll(); conditions[(developerId + developerCount - 1) % developerCount].signalAll(); From e7b2c393396a8ae1565a3a9a4becdf1f4f5bb328 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D0=BB=D0=B0=D0=B3=D0=BE=D1=80=D0=BE=D0=B4=D0=BD?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=9B=D1=8E=D0=B4=D0=BC=D0=B8=D0=BB=D0=B0?= Date: Mon, 22 Sep 2025 22:56:19 +0300 Subject: [PATCH 6/6] =?UTF-8?q?LAB-1=20=D0=94=D0=BE=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=BE=D1=82=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/labs/DinnerProcessingService.java | 47 +++++++++++-------- src/main/java/org/labs/common/AsyncUtils.java | 17 ++----- .../org/labs/kitchen/model/KitchenModel.java | 4 +- .../labs/serverequest/model/ServeRequest.java | 8 +++- .../java/org/labs/state/model/StateModel.java | 3 +- .../org/labs/waiter/model/WaiterModel.java | 13 +++-- 6 files changed, 49 insertions(+), 43 deletions(-) diff --git a/src/main/java/org/labs/DinnerProcessingService.java b/src/main/java/org/labs/DinnerProcessingService.java index 4349223..0b5a5cb 100644 --- a/src/main/java/org/labs/DinnerProcessingService.java +++ b/src/main/java/org/labs/DinnerProcessingService.java @@ -9,6 +9,10 @@ import org.labs.state.model.StateModel; import org.labs.waiter.model.WaiterModel; +import java.util.Arrays; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.IntStream; @@ -16,10 +20,11 @@ public class DinnerProcessingService { private static final int TARGET_RESOURCE_COUNT = Config.DEVELOPER_COUNT; - private static final Thread[] DEVELOPER_THREADS = new Thread[TARGET_RESOURCE_COUNT]; private static final int DISH_COUNT = Config.DISH_COUNT; private static final int WAITER_COUNT = Config.WAITER_COUNT; - private static final Thread[] WAITER_THREADS = new Thread[WAITER_COUNT]; + + private final ExecutorService developerPool = Executors.newWorkStealingPool(TARGET_RESOURCE_COUNT); + private final ExecutorService waiterPool = Executors.newWorkStealingPool(WAITER_COUNT); public DeveloperModel[] runDinner() { var startDate = System.currentTimeMillis(); @@ -30,8 +35,8 @@ public DeveloperModel[] runDinner() { var developers = createDevelopers(spoons, kitchen); var waiters = createWaiters(kitchen); - startDeveloperThreads(developers); - startWaiterThreads(waiters); + startDeveloperTasks(developers); + startWaiterTasks(waiters); monitorDinner(kitchen); @@ -62,24 +67,16 @@ private DeveloperModel[] createDevelopers(SpoonModel[] spoons, KitchenModel kitc private WaiterModel[] createWaiters(KitchenModel kitchen) { var waiters = new WaiterModel[WAITER_COUNT]; - IntStream.range(0, WAITER_COUNT).forEach(number -> { - waiters[number] = new WaiterModel(number, kitchen); - }); + IntStream.range(0, WAITER_COUNT).forEach(number -> waiters[number] = new WaiterModel(number, kitchen)); return waiters; } - private void startWaiterThreads(WaiterModel[] waiters) { - IntStream.range(0, waiters.length).forEach(number -> { - WAITER_THREADS[number] = new Thread(waiters[number], "waiter-" + (number + 1)); - WAITER_THREADS[number].start(); - }); + private void startDeveloperTasks(DeveloperModel[] developers) { + Arrays.stream(developers).forEach(developerPool::submit); } - private void startDeveloperThreads(DeveloperModel[] developers) { - IntStream.range(0, developers.length).forEach(number -> { - DEVELOPER_THREADS[number] = new Thread(developers[number], "developer-" + (number + 1)); - DEVELOPER_THREADS[number].start(); - }); + private void startWaiterTasks(WaiterModel[] waiters) { + Arrays.stream(waiters).forEach(waiterPool::submit); } private void monitorDinner(KitchenModel kitchen) { @@ -91,7 +88,19 @@ private void monitorDinner(KitchenModel kitchen) { private void stopDinner() { AsyncUtils.waitMillis(Config.DINNER_DURATION_IN_MS); - AsyncUtils.stopThreads(WAITER_THREADS); - AsyncUtils.stopThreads(DEVELOPER_THREADS); + shutdownAndAwaitTermination(developerPool); + shutdownAndAwaitTermination(waiterPool); + } + + private void shutdownAndAwaitTermination(ExecutorService pool) { + pool.shutdown(); + try { + if (!pool.awaitTermination(5, TimeUnit.SECONDS)) { + pool.shutdownNow(); + } + } catch (InterruptedException e) { + pool.shutdownNow(); + Thread.currentThread().interrupt(); + } } } diff --git a/src/main/java/org/labs/common/AsyncUtils.java b/src/main/java/org/labs/common/AsyncUtils.java index afe51da..401bd55 100644 --- a/src/main/java/org/labs/common/AsyncUtils.java +++ b/src/main/java/org/labs/common/AsyncUtils.java @@ -2,28 +2,21 @@ import lombok.extern.slf4j.Slf4j; -import java.util.Arrays; +import java.util.concurrent.ThreadLocalRandom; @Slf4j public class AsyncUtils { - public static void waitMillis(long millis) { - if (millis <= 0) { + public static void waitMillis(long maxMillis) { + if (maxMillis <= 0) { return; } try { - Thread.sleep(millis); + long delay = ThreadLocalRandom.current().nextLong(maxMillis + 1); + Thread.sleep(delay); } catch (InterruptedException e) { log.warn("Поток {} был прерван", Thread.currentThread().getName()); Thread.currentThread().interrupt(); } } - - public static void stopThreads(Thread[] threads) { - Arrays.stream(threads) - .filter(t -> !t.isInterrupted()) - .forEach(Thread::interrupt); - - waitMillis(100); - } } diff --git a/src/main/java/org/labs/kitchen/model/KitchenModel.java b/src/main/java/org/labs/kitchen/model/KitchenModel.java index d972fca..ba05d0a 100644 --- a/src/main/java/org/labs/kitchen/model/KitchenModel.java +++ b/src/main/java/org/labs/kitchen/model/KitchenModel.java @@ -4,14 +4,14 @@ import org.labs.serverequest.model.ServeRequest; import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.atomic.AtomicInteger; @RequiredArgsConstructor public class KitchenModel { private final AtomicInteger remainingDishCount; - private final BlockingQueue requestQueue = new LinkedBlockingQueue<>(); + private final BlockingQueue requestQueue = new PriorityBlockingQueue<>(); public void submitRequest(ServeRequest req) throws InterruptedException { requestQueue.put(req); diff --git a/src/main/java/org/labs/serverequest/model/ServeRequest.java b/src/main/java/org/labs/serverequest/model/ServeRequest.java index ccd9009..6f13ab5 100644 --- a/src/main/java/org/labs/serverequest/model/ServeRequest.java +++ b/src/main/java/org/labs/serverequest/model/ServeRequest.java @@ -7,7 +7,13 @@ @Getter @RequiredArgsConstructor -public class ServeRequest { +public class ServeRequest implements Comparable { private final int developerId; + private final long timestamp = System.nanoTime(); private final CompletableFuture served = new CompletableFuture<>(); + + @Override + public int compareTo(ServeRequest other) { + return Long.compare(this.timestamp, other.timestamp); + } } diff --git a/src/main/java/org/labs/state/model/StateModel.java b/src/main/java/org/labs/state/model/StateModel.java index 7501794..1fdb83c 100644 --- a/src/main/java/org/labs/state/model/StateModel.java +++ b/src/main/java/org/labs/state/model/StateModel.java @@ -16,13 +16,12 @@ public class StateModel { private final int developerCount; private final DeveloperModel[] developers; private final State[] state; - private final Lock lock; + private final Lock lock = new ReentrantLock();; private final Condition[] conditions; public StateModel(DeveloperModel[] developers) { this.developers = developers; this.developerCount = developers.length; - lock = new ReentrantLock(); state = new State[developerCount]; conditions = new Condition[developerCount]; IntStream.range(0, developerCount).forEach(i -> { diff --git a/src/main/java/org/labs/waiter/model/WaiterModel.java b/src/main/java/org/labs/waiter/model/WaiterModel.java index 6f6f49a..15e59eb 100644 --- a/src/main/java/org/labs/waiter/model/WaiterModel.java +++ b/src/main/java/org/labs/waiter/model/WaiterModel.java @@ -14,21 +14,20 @@ public class WaiterModel implements Runnable { @Override public void run() { try { - while (true) { + while (!(kitchen.isDepleted() && kitchen.isRequestQueueEmpty())) { var serveRequest = kitchen.takeRequest(); if (serveRequest == null) { continue; } var isServed = kitchen.takeDishIfAvailable(); - log.debug("Официант {} начал обслуживание разработчика {} -> {}", id + 1, serveRequest.getDeveloperId() + 1, isServed); - serveRequest.getServed().complete(isServed); + log.debug("Официант {} начал обслуживание разработчика {} -> {}", + id + 1, serveRequest.getDeveloperId() + 1, isServed); - if (!isServed && kitchen.isDepleted() && kitchen.isRequestQueueEmpty()) { - log.info("Официант {} разнес все блюда, заказы закончились", id + 1); - break; - } + serveRequest.getServed().complete(isServed); } + + log.info("Официант {} разнес все блюда, заказы закончились", id + 1); } catch (InterruptedException e) { log.warn("Поток {} был прерван", Thread.currentThread().getName()); Thread.currentThread().interrupt();