From ab66e3fea77770e40a322c4efe70bcb5d51a8e6c Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 12:38:26 +0000 Subject: [PATCH 1/5] 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 b8b09bfdb286f2a50d630203178438de17464b25 Mon Sep 17 00:00:00 2001 From: ArtiKhog Date: Wed, 1 Oct 2025 00:01:59 +0300 Subject: [PATCH 2/5] it's working! --- .idea/gradle.xml | 16 +++ build.gradle.kts | 1 + src/main/java/org/labs/Main.java | 24 ++++- src/main/java/org/labs/config/AppConfig.java | 42 ++++++++ src/main/java/org/labs/model/Kitchen.java | 28 +++++ src/main/java/org/labs/model/Programmer.java | 104 +++++++++++++++++++ src/main/java/org/labs/model/Restaurant.java | 100 ++++++++++++++++++ src/main/java/org/labs/model/Spoon.java | 29 ++++++ src/main/java/org/labs/model/Waiter.java | 67 ++++++++++++ src/main/resources/config.properties | 3 + 10 files changed, 413 insertions(+), 1 deletion(-) create mode 100644 .idea/gradle.xml create mode 100644 src/main/java/org/labs/config/AppConfig.java create mode 100644 src/main/java/org/labs/model/Kitchen.java create mode 100644 src/main/java/org/labs/model/Programmer.java create mode 100644 src/main/java/org/labs/model/Restaurant.java create mode 100644 src/main/java/org/labs/model/Spoon.java create mode 100644 src/main/java/org/labs/model/Waiter.java create mode 100644 src/main/resources/config.properties diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..ce1c62c --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index bda0d97..f76b531 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,6 +10,7 @@ repositories { } dependencies { + implementation("org.openjdk.jcstress:jcstress-core:0.16") 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..49c9b5b 100644 --- a/src/main/java/org/labs/Main.java +++ b/src/main/java/org/labs/Main.java @@ -1,7 +1,29 @@ package org.labs; +import org.labs.config.AppConfig; +import org.labs.model.Restaurant; + public class Main { + public static void main(String[] args) { - System.out.println("Hello, World!"); + int numProgrammers = AppConfig.getNumProgrammers(); + int numWaiters = AppConfig.getNumWaiters(); + int maxMeals = AppConfig.getMaxMeals(); + + Restaurant restaurant = new Restaurant(maxMeals, numProgrammers, numWaiters); + + long startTime = System.currentTimeMillis(); + restaurant.startDinner(); + + + restaurant.awaitCompletion(); + + long endTime = System.currentTimeMillis(); + long duration = endTime - startTime; + + restaurant.printStatistics(); + + System.out.println("\nTotal dinner time: " + duration + " ms"); + System.out.println("Dinner completed successfully!"); } } \ No newline at end of file diff --git a/src/main/java/org/labs/config/AppConfig.java b/src/main/java/org/labs/config/AppConfig.java new file mode 100644 index 0000000..752457b --- /dev/null +++ b/src/main/java/org/labs/config/AppConfig.java @@ -0,0 +1,42 @@ +package org.labs.config; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +public class AppConfig { + private static final Properties props = new Properties(); + + static { + try (InputStream input = AppConfig.class.getClassLoader() + .getResourceAsStream("config.properties")) { + if (input == null) { + System.err.println("Config file not found! Using defaults"); + setDefaults(); + } else { + props.load(input); + } + } catch (IOException e) { + System.err.println("Error loading config: " + e.getMessage()); + setDefaults(); + } + } + + private static void setDefaults() { + props.setProperty("num.programmers", "7"); + props.setProperty("num.waiters", "3"); + props.setProperty("max.meals", "1_000_000"); + } + + public static int getNumProgrammers() { + return Integer.parseInt(props.getProperty("num.programmers")); + } + + public static int getNumWaiters() { + return Integer.parseInt(props.getProperty("num.waiters")); + } + + public static int getMaxMeals() { + return Integer.parseInt(props.getProperty("max.meals")); + } +} diff --git a/src/main/java/org/labs/model/Kitchen.java b/src/main/java/org/labs/model/Kitchen.java new file mode 100644 index 0000000..e4ed3c5 --- /dev/null +++ b/src/main/java/org/labs/model/Kitchen.java @@ -0,0 +1,28 @@ +package org.labs.model; + +import java.util.concurrent.atomic.AtomicInteger; + +public class Kitchen { + private final AtomicInteger remainingMeals; + private volatile boolean isOpen = true; + + public Kitchen(int totalMeals) { + this.remainingMeals = new AtomicInteger(totalMeals); + } + + public boolean tryTakeMeal() { + return remainingMeals.getAndUpdate(current -> current > 0 ? current - 1 : current) > 0; + } + + public boolean hasMeals() { + return remainingMeals.get() > 0 && isOpen; + } + + public int getRemainingMeals() { + return remainingMeals.get(); + } + + public void close() { + isOpen = false; + } +} diff --git a/src/main/java/org/labs/model/Programmer.java b/src/main/java/org/labs/model/Programmer.java new file mode 100644 index 0000000..c1cd53b --- /dev/null +++ b/src/main/java/org/labs/model/Programmer.java @@ -0,0 +1,104 @@ +package org.labs.model; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +class Programmer implements Runnable { + private final int id; + private final Spoon firstSpoon; + private final Spoon secondSpoon; + private final Waiter waiter; + private final AtomicInteger mealsEaten = new AtomicInteger(0); + private final AtomicBoolean permissionToEat = new AtomicBoolean(false); + private volatile boolean running = true; + + public Programmer(int id, Spoon leftSpoon, Spoon rightSpoon, Waiter waiter) { + this.id = id; + this.waiter = waiter; + + // Определяем порядок взятия ложек для избежания deadlock + if (leftSpoon.getId() < rightSpoon.getId()) { + this.firstSpoon = leftSpoon; + this.secondSpoon = rightSpoon; + } else { + this.firstSpoon = rightSpoon; + this.secondSpoon = leftSpoon; + } + } + + @Override + public void run() { + try { + while (running && waiter.isKitchenOpen()) { + think(); + requestToEat(); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + System.out.println("Programmer " + id + " finished. Eaten: " + mealsEaten.get() + " meals"); + } + } + + private void think() throws InterruptedException { + System.out.println("Programmer " + id + " is thinking"); + } + + private void requestToEat() throws InterruptedException { + if (!running || !waiter.isKitchenOpen()) { + return; + } + + permissionToEat.set(false); + waiter.placeOrder(this); + + synchronized (this) { + while (!permissionToEat.get() && running && waiter.isKitchenOpen()) { + wait(200); + if (!permissionToEat.get() && waiter.isKitchenOpen()) { + waiter.placeOrder(this); + } + } + } + + if (permissionToEat.get() && running) { + eat(); + } + } + + private void eat() throws InterruptedException { + System.out.println("Programmer " + id + " has food, waiting for spoons..."); + + firstSpoon.acquire(); + try { + secondSpoon.acquire(); + try { + System.out.println("Programmer " + id + " is eating (" + (mealsEaten.get() + 1) + " meal)"); + mealsEaten.incrementAndGet(); + System.out.println("Programmer " + id + " finished eating"); + } finally { + secondSpoon.release(); + } + } finally { + firstSpoon.release(); + } + } + + public void receiveMeal() { + permissionToEat.set(true); + synchronized (this) { + notify(); + } + } + + public void stop() { + running = false; + synchronized (this) { + notifyAll(); + } + } + + public int getMealsEaten() { return mealsEaten.get(); } + public void setPermissionToEat(boolean permission) { permissionToEat.set(permission); } + public int getId() { return id; } +} diff --git a/src/main/java/org/labs/model/Restaurant.java b/src/main/java/org/labs/model/Restaurant.java new file mode 100644 index 0000000..233ef94 --- /dev/null +++ b/src/main/java/org/labs/model/Restaurant.java @@ -0,0 +1,100 @@ +package org.labs.model; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +public class Restaurant { + private final Kitchen kitchen; + private final Spoon[] spoons; + private final Programmer[] programmers; + private final Waiter[] waiters; + private final ExecutorService executor; + + public Restaurant(int totalMeals, int numProgrammers, int numWaiters) { + // Создаем кухню + this.kitchen = new Kitchen(totalMeals); + + // Создаем ложки + this.spoons = new Spoon[numProgrammers]; + for (int i = 0; i < numProgrammers; i++) { + spoons[i] = new Spoon(i); + } + + // Создаем официантов + this.waiters = new Waiter[numWaiters]; + for (int i = 0; i < numWaiters; i++) { + waiters[i] = new Waiter(i, kitchen); + } + + // Создаем программистов и распределяем по официантам + this.programmers = new Programmer[numProgrammers]; + for (int i = 0; i < numProgrammers; i++) { + Spoon leftSpoon = spoons[i]; + Spoon rightSpoon = spoons[(i + 1) % numProgrammers]; + Waiter assignedWaiter = waiters[i % numWaiters]; + programmers[i] = new Programmer(i, leftSpoon, rightSpoon, assignedWaiter); + } + // можно запустить executor на виртуальных потоках + // this.executor = Executors.newVirtualThreadPerTaskExecutor(); + this.executor = Executors.newFixedThreadPool(numProgrammers + numWaiters); + } + + // Ресторан работает так, что программисты перед поеданием супа просят официанта принести еду + // Официант работает как очередь заказов, а кухня предоставляет доступ к еде для официантов + public void startDinner() { + for (Waiter waiter : waiters) { + executor.execute(waiter); + } + for (Programmer programmer : programmers) { + executor.execute(programmer); + } + } + + public void awaitCompletion() { + executor.shutdown(); + try { + if (!executor.awaitTermination(5, TimeUnit.MINUTES)) { + System.err.println("Dinner timed out! Forcing shutdown..."); + executor.shutdownNow(); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + kitchen.close(); + } + } + + public void stopDinner() { + kitchen.close(); + for (Programmer programmer : programmers) { + programmer.stop(); + } + for (Waiter waiter : waiters) { + waiter.stop(); + } + executor.shutdownNow(); + } + + public void printStatistics() { + int totalEaten = 0; + int minEaten = Integer.MAX_VALUE; + int maxEaten = Integer.MIN_VALUE; + + for (Programmer programmer : programmers) { + int eaten = programmer.getMealsEaten(); + totalEaten += eaten; + minEaten = Math.min(minEaten, eaten); + maxEaten = Math.max(maxEaten, eaten); + } + + double averageEaten = (double) totalEaten / programmers.length; + + System.out.printf("Remaining meals: %d%n", kitchen.getRemainingMeals()); + System.out.printf("Total meals eaten: %d%n", totalEaten); + System.out.printf("Programmers: %d%n", programmers.length); + System.out.printf("Average per programmer: %.2f meals%n", averageEaten); + System.out.printf("Range: %d - %d meals%n", minEaten, maxEaten); + System.out.printf("Difference: %d meals%n", maxEaten - minEaten); + } +} diff --git a/src/main/java/org/labs/model/Spoon.java b/src/main/java/org/labs/model/Spoon.java new file mode 100644 index 0000000..4ef7bf1 --- /dev/null +++ b/src/main/java/org/labs/model/Spoon.java @@ -0,0 +1,29 @@ +package org.labs.model; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class Spoon { + private final Lock lock = new ReentrantLock(true); + private final int id; + + public Spoon(int id) { + this.id = id; + } + + public boolean tryAcquire() { + return lock.tryLock(); + } + + public void acquire() throws InterruptedException { + lock.lockInterruptibly(); + } + + public void release() { + lock.unlock(); + } + + public int getId() { + return id; + } +} \ No newline at end of file diff --git a/src/main/java/org/labs/model/Waiter.java b/src/main/java/org/labs/model/Waiter.java new file mode 100644 index 0000000..e8587ea --- /dev/null +++ b/src/main/java/org/labs/model/Waiter.java @@ -0,0 +1,67 @@ +package org.labs.model; + +import java.util.concurrent.LinkedBlockingQueue; + +public class Waiter implements Runnable { + private final int id; + private final Kitchen kitchen; + private final LinkedBlockingQueue programmerQueue; + private volatile boolean running = true; + + public Waiter(int id, Kitchen kitchen) { + this.id = id; + this.kitchen = kitchen; + this.programmerQueue = new LinkedBlockingQueue<>(); + } + + public void placeOrder(Programmer programmer) { + if (running && kitchen.hasMeals()) { + programmerQueue.offer(programmer); + } + } + + public boolean isKitchenOpen() { + return kitchen.hasMeals() && running; + } + + @Override + public void run() { + System.out.println("Waiter " + id + " start working"); + + while (running && kitchen.hasMeals()) { + Programmer programmer = programmerQueue.poll(); + if (programmer == null) { + continue; + } + + // Пытаемся получить еду с кухни + if (kitchen.tryTakeMeal()) { + System.out.println("Waiter " + id + " serving meal to programmer " + programmer.getId()); + + // Сообщаем программисту, что еда готова + programmer.receiveMeal(); + } else { + programmerQueue.offer(programmer); + } + + } + + notifyAllprogrammers(); + System.out.println("Waiter " + id + " finished"); + } + + // после окончания работы очищаем очередь программистов + private void notifyAllprogrammers() { + Programmer programmer; + while ((programmer = programmerQueue.poll()) != null) { + programmer.setPermissionToEat(false); + synchronized (programmer) { + programmer.notify(); + } + } + } + + public void stop() { + running = false; + } +} diff --git a/src/main/resources/config.properties b/src/main/resources/config.properties new file mode 100644 index 0000000..9d8cf3d --- /dev/null +++ b/src/main/resources/config.properties @@ -0,0 +1,3 @@ +num.programmers=100 +num.waiters=3 +max.meals=10000 \ No newline at end of file From 18843bfd605c41c50247c272a44fd0a721d39d69 Mon Sep 17 00:00:00 2001 From: ArtiKhog Date: Thu, 2 Oct 2025 17:18:51 +0300 Subject: [PATCH 3/5] add configuring threadPool add sleep on eat and thinking remove unnecessary reordering by programmer --- build.gradle.kts | 4 ++ src/main/java/org/labs/Main.java | 5 +- src/main/java/org/labs/config/AppConfig.java | 10 +++ .../java/org/labs/config/ExecutorType.java | 8 +++ src/main/java/org/labs/model/Programmer.java | 11 ++-- src/main/java/org/labs/model/Restaurant.java | 62 +++++++++++++++++-- src/main/resources/config.properties | 9 ++- 7 files changed, 97 insertions(+), 12 deletions(-) create mode 100644 src/main/java/org/labs/config/ExecutorType.java diff --git a/build.gradle.kts b/build.gradle.kts index f76b531..974a504 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,8 +13,12 @@ dependencies { implementation("org.openjdk.jcstress:jcstress-core:0.16") testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") + testImplementation ("org.junit.jupiter:junit-jupiter-params:5.9.3") } tasks.test { useJUnitPlatform() + testLogging { + showStandardStreams = false + } } \ 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 49c9b5b..7528906 100644 --- a/src/main/java/org/labs/Main.java +++ b/src/main/java/org/labs/Main.java @@ -1,6 +1,7 @@ package org.labs; import org.labs.config.AppConfig; +import org.labs.config.ExecutorType; import org.labs.model.Restaurant; public class Main { @@ -9,8 +10,8 @@ public static void main(String[] args) { int numProgrammers = AppConfig.getNumProgrammers(); int numWaiters = AppConfig.getNumWaiters(); int maxMeals = AppConfig.getMaxMeals(); - - Restaurant restaurant = new Restaurant(maxMeals, numProgrammers, numWaiters); + ExecutorType executorType = AppConfig.getExecutorType(); + Restaurant restaurant = new Restaurant(maxMeals, numProgrammers, numWaiters, executorType); long startTime = System.currentTimeMillis(); restaurant.startDinner(); diff --git a/src/main/java/org/labs/config/AppConfig.java b/src/main/java/org/labs/config/AppConfig.java index 752457b..bd4433e 100644 --- a/src/main/java/org/labs/config/AppConfig.java +++ b/src/main/java/org/labs/config/AppConfig.java @@ -26,6 +26,7 @@ private static void setDefaults() { props.setProperty("num.programmers", "7"); props.setProperty("num.waiters", "3"); props.setProperty("max.meals", "1_000_000"); + props.setProperty("executor.type", "VIRTUAL_THREADS"); } public static int getNumProgrammers() { @@ -39,4 +40,13 @@ public static int getNumWaiters() { public static int getMaxMeals() { return Integer.parseInt(props.getProperty("max.meals")); } + + public static ExecutorType getExecutorType() { + try { + return ExecutorType.valueOf(props.getProperty("executor.type").toUpperCase()); + } catch (IllegalArgumentException e) { + System.err.println("Invalid executor type in config, using VIRTUAL_THREADS"); + return ExecutorType.VIRTUAL_THREADS; + } + } } diff --git a/src/main/java/org/labs/config/ExecutorType.java b/src/main/java/org/labs/config/ExecutorType.java new file mode 100644 index 0000000..d880460 --- /dev/null +++ b/src/main/java/org/labs/config/ExecutorType.java @@ -0,0 +1,8 @@ +package org.labs.config; + +public enum ExecutorType { + VIRTUAL_THREADS, + FIXED_THREAD_POOL, + CACHED_THREAD_POOL, + WORK_STEALING_POOL, +} diff --git a/src/main/java/org/labs/model/Programmer.java b/src/main/java/org/labs/model/Programmer.java index c1cd53b..7ad2647 100644 --- a/src/main/java/org/labs/model/Programmer.java +++ b/src/main/java/org/labs/model/Programmer.java @@ -1,5 +1,6 @@ package org.labs.model; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -42,6 +43,7 @@ public void run() { private void think() throws InterruptedException { System.out.println("Programmer " + id + " is thinking"); + Thread.sleep(ThreadLocalRandom.current().nextInt(100, 200)); } private void requestToEat() throws InterruptedException { @@ -53,11 +55,8 @@ private void requestToEat() throws InterruptedException { waiter.placeOrder(this); synchronized (this) { - while (!permissionToEat.get() && running && waiter.isKitchenOpen()) { - wait(200); - if (!permissionToEat.get() && waiter.isKitchenOpen()) { - waiter.placeOrder(this); - } + if (!permissionToEat.get()) { + wait(5000); // Долгий таймаут, просто чтобы не блокировать навсегда } } @@ -72,6 +71,8 @@ private void eat() throws InterruptedException { firstSpoon.acquire(); try { secondSpoon.acquire(); + + Thread.sleep(ThreadLocalRandom.current().nextInt(100, 200)); try { System.out.println("Programmer " + id + " is eating (" + (mealsEaten.get() + 1) + " meal)"); mealsEaten.incrementAndGet(); diff --git a/src/main/java/org/labs/model/Restaurant.java b/src/main/java/org/labs/model/Restaurant.java index 233ef94..31fe6b5 100644 --- a/src/main/java/org/labs/model/Restaurant.java +++ b/src/main/java/org/labs/model/Restaurant.java @@ -1,5 +1,7 @@ package org.labs.model; +import org.labs.config.ExecutorType; + import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @@ -11,7 +13,7 @@ public class Restaurant { private final Waiter[] waiters; private final ExecutorService executor; - public Restaurant(int totalMeals, int numProgrammers, int numWaiters) { + public Restaurant(int totalMeals, int numProgrammers, int numWaiters, ExecutorType executorType) { // Создаем кухню this.kitchen = new Kitchen(totalMeals); @@ -35,9 +37,9 @@ public Restaurant(int totalMeals, int numProgrammers, int numWaiters) { Waiter assignedWaiter = waiters[i % numWaiters]; programmers[i] = new Programmer(i, leftSpoon, rightSpoon, assignedWaiter); } - // можно запустить executor на виртуальных потоках - // this.executor = Executors.newVirtualThreadPerTaskExecutor(); - this.executor = Executors.newFixedThreadPool(numProgrammers + numWaiters); + + // Создаем executor по переданному типу + this.executor = createExecutor(executorType, numProgrammers, numWaiters); } // Ресторан работает так, что программисты перед поеданием супа просят официанта принести еду @@ -97,4 +99,56 @@ public void printStatistics() { System.out.printf("Range: %d - %d meals%n", minEaten, maxEaten); System.out.printf("Difference: %d meals%n", maxEaten - minEaten); } + + public int getTotalMealsEaten() { + int totalEaten = 0; + + for (Programmer programmer : programmers) { + int eaten = programmer.getMealsEaten(); + totalEaten += eaten; + } + return totalEaten; + } + + public int getEatenMealDifference() { + if (programmers == null || programmers.length == 0) { + return 0; + } + + int minMeals = Integer.MAX_VALUE; + int maxMeals = Integer.MIN_VALUE; + + for (Programmer programmer : programmers) { + int mealsEaten = programmer.getMealsEaten(); + minMeals = Math.min(minMeals, mealsEaten); + maxMeals = Math.max(maxMeals, mealsEaten); + } + + return maxMeals - minMeals; + } + + private ExecutorService createExecutor(ExecutorType type, int numProgrammers, int numWaiters) { + switch (type) { + case VIRTUAL_THREADS: + System.out.println("Using Virtual Threads"); + return Executors.newVirtualThreadPerTaskExecutor(); + + case FIXED_THREAD_POOL: + int poolSize = numProgrammers + numWaiters; + System.out.println("Using Fixed Thread Pool: " + poolSize + " threads"); + return Executors.newFixedThreadPool(poolSize); + + case CACHED_THREAD_POOL: + System.out.println("Using Cached Thread Pool"); + return Executors.newCachedThreadPool(); + + case WORK_STEALING_POOL: + int parallelism = Math.max(1, Runtime.getRuntime().availableProcessors()); + System.out.println("Using Work Stealing Pool: " + parallelism + " parallelism"); + return Executors.newWorkStealingPool(parallelism); + + default: + throw new IllegalArgumentException("Unknown executor type: " + type); + } + } } diff --git a/src/main/resources/config.properties b/src/main/resources/config.properties index 9d8cf3d..e691cc8 100644 --- a/src/main/resources/config.properties +++ b/src/main/resources/config.properties @@ -1,3 +1,10 @@ num.programmers=100 num.waiters=3 -max.meals=10000 \ No newline at end of file +max.meals=100000 + + +executor.type=VIRTUAL_THREADS +# executor.type=FIXED_THREAD_POOL +# executor.type=CACHED_THREAD_POOL +# executor.type=WORK_STEALING_POOL +# executor.type=SINGLE_THREAD \ No newline at end of file From 00dcf00190b3e1e1a0190fff50c3a1711eae996a Mon Sep 17 00:00:00 2001 From: ArtiKhog Date: Thu, 2 Oct 2025 17:19:06 +0300 Subject: [PATCH 4/5] add tests --- src/test/java/ExecutorPerformanceTest.java | 38 +++++++++++++++++++ src/test/java/MealLoadPerformanceTest.java | 44 ++++++++++++++++++++++ src/test/java/RestaurantTest.java | 30 +++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 src/test/java/ExecutorPerformanceTest.java create mode 100644 src/test/java/MealLoadPerformanceTest.java create mode 100644 src/test/java/RestaurantTest.java diff --git a/src/test/java/ExecutorPerformanceTest.java b/src/test/java/ExecutorPerformanceTest.java new file mode 100644 index 0000000..d271df3 --- /dev/null +++ b/src/test/java/ExecutorPerformanceTest.java @@ -0,0 +1,38 @@ +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.labs.config.ExecutorType; +import org.labs.model.Restaurant; + +import java.io.*; + +import static org.junit.jupiter.api.Assertions.*; + +class ExecutorPerformanceTest { + @ParameterizedTest + @EnumSource(ExecutorType.class) + void testAllExecutorTypes(ExecutorType executorType) throws InterruptedException { + PrintStream originalOut = System.out; + try { + System.setOut(new PrintStream(new ByteArrayOutputStream())); + + Restaurant restaurant = new Restaurant(1000, 50, 5, executorType); + + long startTime = System.nanoTime(); + restaurant.startDinner(); + restaurant.awaitCompletion(); + long endTime = System.nanoTime(); + + long durationMs = (endTime - startTime) / 1_000_000; + int mealsEaten = restaurant.getTotalMealsEaten(); + int mealsDifference = restaurant.getEatenMealDifference(); + + System.setOut(originalOut); + System.out.printf("%s: %d ms, %d meals %d difference %n", executorType, durationMs, mealsEaten, mealsDifference); + + assertTrue(mealsEaten > 0, "Should have eaten some meals"); + + } finally { + System.setOut(originalOut); + } + } +} \ No newline at end of file diff --git a/src/test/java/MealLoadPerformanceTest.java b/src/test/java/MealLoadPerformanceTest.java new file mode 100644 index 0000000..ba83e76 --- /dev/null +++ b/src/test/java/MealLoadPerformanceTest.java @@ -0,0 +1,44 @@ +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.labs.config.ExecutorType; +import org.labs.model.Restaurant; + +import java.io.*; + +import static org.junit.jupiter.api.Assertions.*; + +class MealLoadPerformanceTest { + @ParameterizedTest + @ValueSource(ints = {100, 1000, 10_000, 20_000}) + void testWithMealAmount(int mealAmount) { + PrintStream originalOut = System.out; + try { + System.setOut(new PrintStream(new ByteArrayOutputStream())); + + int numProgrammers = 100; + int numWaiters = 10; + ExecutorType executorType = ExecutorType.FIXED_THREAD_POOL; + + Restaurant restaurant = new Restaurant(mealAmount, numProgrammers, numWaiters, executorType); + + long startTime = System.nanoTime(); + restaurant.startDinner(); + restaurant.awaitCompletion(); + long endTime = System.nanoTime(); + + long durationMs = (endTime - startTime) / 1_000_000; + int mealsEaten = restaurant.getTotalMealsEaten(); + int mealsDifference = restaurant.getEatenMealDifference(); + double mealsPerSecond = (double) mealsEaten / (durationMs / 1000.0); + + System.setOut(originalOut); + System.out.printf("%s with %d meals: %d difference %d ms (%.2f meals/sec)%n", + executorType, mealAmount, mealsDifference, durationMs, mealsPerSecond); + + assertEquals(mealAmount, mealsEaten, "All meals should be eaten"); + + } finally { + System.setOut(originalOut); + } + } +} \ No newline at end of file diff --git a/src/test/java/RestaurantTest.java b/src/test/java/RestaurantTest.java new file mode 100644 index 0000000..a30290e --- /dev/null +++ b/src/test/java/RestaurantTest.java @@ -0,0 +1,30 @@ + + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.labs.config.ExecutorType; +import org.labs.model.Restaurant; + +import static org.junit.jupiter.api.Assertions.*; + +class RestaurantTest { + + private Restaurant restaurant; + + @BeforeEach + void setUp() { + restaurant = new Restaurant(100, 5, 2, ExecutorType.VIRTUAL_THREADS); + } + + @Test + @DisplayName("Все порции должны быть съедены") + void testAllMealsAreEaten() { + restaurant.startDinner(); + restaurant.awaitCompletion(); + + int totalEaten = restaurant.getTotalMealsEaten(); + assertTrue(totalEaten > 0, "Должны быть съедены порции"); + assertTrue(totalEaten <= 100, "Нельзя съесть больше порций чем есть"); + } +} From 8186cae6249115ca813af74d761ec748a0c1946f Mon Sep 17 00:00:00 2001 From: ArtiKhog Date: Sun, 5 Oct 2025 22:54:53 +0300 Subject: [PATCH 5/5] add PriorityBlockingQueue instead of LinkedBlockingQueue --- src/main/java/org/labs/model/Programmer.java | 5 +++++ src/main/java/org/labs/model/Waiter.java | 8 +++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/labs/model/Programmer.java b/src/main/java/org/labs/model/Programmer.java index 7ad2647..e5a004a 100644 --- a/src/main/java/org/labs/model/Programmer.java +++ b/src/main/java/org/labs/model/Programmer.java @@ -1,5 +1,6 @@ package org.labs.model; +import java.util.Comparator; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -99,6 +100,10 @@ public void stop() { } } + public static Comparator getMealsEatenComparator() { + return Comparator.comparingInt(Programmer::getMealsEaten); + } + public int getMealsEaten() { return mealsEaten.get(); } public void setPermissionToEat(boolean permission) { permissionToEat.set(permission); } public int getId() { return id; } diff --git a/src/main/java/org/labs/model/Waiter.java b/src/main/java/org/labs/model/Waiter.java index e8587ea..ffbb45b 100644 --- a/src/main/java/org/labs/model/Waiter.java +++ b/src/main/java/org/labs/model/Waiter.java @@ -1,17 +1,19 @@ package org.labs.model; -import java.util.concurrent.LinkedBlockingQueue; +import java.util.Collection; +import java.util.PriorityQueue; +import java.util.concurrent.PriorityBlockingQueue; public class Waiter implements Runnable { private final int id; private final Kitchen kitchen; - private final LinkedBlockingQueue programmerQueue; + private final PriorityBlockingQueue programmerQueue; private volatile boolean running = true; public Waiter(int id, Kitchen kitchen) { this.id = id; this.kitchen = kitchen; - this.programmerQueue = new LinkedBlockingQueue<>(); + this.programmerQueue = new PriorityBlockingQueue<>(10, Programmer.getMealsEatenComparator()); } public void placeOrder(Programmer programmer) {