From e4ae1a90c91a7968bf55173160ae73b687fe8190 Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Tue, 16 Sep 2025 07:17:57 +0000 Subject: [PATCH 1/2] 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 f489ccc93270497e08706ef3975431b99664407a Mon Sep 17 00:00:00 2001 From: sidey383 Date: Tue, 23 Sep 2025 09:53:58 +0300 Subject: [PATCH 2/2] The whole project in one commit --- src/main/java/org/labs/Kitchen.java | 17 +++++ src/main/java/org/labs/Main.java | 54 ++++++++++++++- src/main/java/org/labs/Plate.java | 50 ++++++++++++++ src/main/java/org/labs/Programmer.java | 95 ++++++++++++++++++++++++++ src/main/java/org/labs/Spoon.java | 19 ++++++ src/main/java/org/labs/Waiter.java | 30 ++++++++ src/test/java/org/labs/BaseTests.java | 37 ++++++++++ src/test/java/org/labs/TestTable.java | 68 ++++++++++++++++++ 8 files changed, 369 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/labs/Kitchen.java create mode 100644 src/main/java/org/labs/Plate.java create mode 100644 src/main/java/org/labs/Programmer.java create mode 100644 src/main/java/org/labs/Spoon.java create mode 100644 src/main/java/org/labs/Waiter.java create mode 100644 src/test/java/org/labs/BaseTests.java create mode 100644 src/test/java/org/labs/TestTable.java diff --git a/src/main/java/org/labs/Kitchen.java b/src/main/java/org/labs/Kitchen.java new file mode 100644 index 0000000..e0c003e --- /dev/null +++ b/src/main/java/org/labs/Kitchen.java @@ -0,0 +1,17 @@ +package org.labs; + +import java.util.concurrent.atomic.AtomicLong; + +public class Kitchen { + + private final AtomicLong foodPortions; + + public Kitchen(long foodPortionCount) { + this.foodPortions = new AtomicLong(foodPortionCount); + } + + public boolean cookFood() { + return foodPortions.getAndDecrement() > 0; + } + +} diff --git a/src/main/java/org/labs/Main.java b/src/main/java/org/labs/Main.java index 9917247..4f80ac8 100644 --- a/src/main/java/org/labs/Main.java +++ b/src/main/java/org/labs/Main.java @@ -1,7 +1,59 @@ package org.labs; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.stream.LongStream; + public class Main { + + private static final Duration talkDuration = Duration.ofNanos(100); + private static final long programmerCount = 3; + private static final long foodCount = 10_000; + private static final long waiterCount = 3; + public static void main(String[] args) { - System.out.println("Hello, World!"); + Kitchen kitchen = new Kitchen(foodCount); + List waiters = LongStream.range(0, waiterCount) + .mapToObj(num -> new Waiter(kitchen)) + .toList(); + List spoons = LongStream.range(0, programmerCount) + .mapToObj(num -> new Spoon()) + .toList(); + List programmers = LongStream.range(0, programmerCount) + .mapToObj(num -> new Programmer( + waiters, + spoons.get((int) (num % spoons.size())), + spoons.get((int) ((num + 1) % spoons.size())), + new Random(), + talkDuration + )) + .toList(); + List threads = new ArrayList<>(); + for (Programmer prog : programmers) { + Thread t = new Thread(prog); + threads.add(t); + t.start(); + } + threads.forEach((t) -> { + try { + t.join(); + } catch (InterruptedException e) { + throw new RuntimeException("Main thread interrupted"); + } + }); + long waiterSum = 0; + for (var waiter : waiters) { + System.out.printf("Waiter deliver %d portions\n", waiter.getDeliveredFoodCount()); + waiterSum += waiter.getDeliveredFoodCount(); + } + System.out.printf("Waiter total deliver %d\n", waiterSum); + long programmerEat = 0; + for (var programmer : programmers) { + System.out.printf("Programmer eat %d portions\n", programmer.getTotalEat()); + programmerEat += programmer.getTotalEat(); + } + System.out.printf("Programmers total eat %d portions\n", programmerEat); } } \ No newline at end of file diff --git a/src/main/java/org/labs/Plate.java b/src/main/java/org/labs/Plate.java new file mode 100644 index 0000000..25b3354 --- /dev/null +++ b/src/main/java/org/labs/Plate.java @@ -0,0 +1,50 @@ +package org.labs; + +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class Plate { + + private static final Logger logger = Logger.getLogger("Plate"); + + private final AtomicReference foodStatus = new AtomicReference<>(FoodStatus.NO_FOOD); + + public enum FoodStatus { + NO_FOOD, + AWAIT_FOOD, + HAVE_FOOD, + NO_MORE_FOOD + } + + public FoodStatus getStatus() { + return foodStatus.get(); + } + + public void startAwaitFood() { + var exchangeResult = foodStatus.compareAndExchange(FoodStatus.NO_FOOD, FoodStatus.AWAIT_FOOD); + if (exchangeResult != FoodStatus.NO_FOOD) { + logger.log(Level.WARNING, "Start await food in not expected status " + exchangeResult); + } + } + + public void receiveFood() { + var exchangeResult = foodStatus.compareAndExchange(FoodStatus.AWAIT_FOOD, FoodStatus.HAVE_FOOD); + if (exchangeResult != FoodStatus.AWAIT_FOOD) { + logger.log(Level.WARNING, "Receive food in not expected status " + exchangeResult); + } + } + + public void noMoreFood() { + var exchangeResult = foodStatus.compareAndExchange(FoodStatus.AWAIT_FOOD, FoodStatus.NO_MORE_FOOD); + if (exchangeResult != FoodStatus.AWAIT_FOOD) { + logger.log(Level.WARNING, "Mark status no more food in not expected status " + exchangeResult); + } + } + + public boolean consumeFood() { + var exchangeResult = foodStatus.compareAndExchange(FoodStatus.HAVE_FOOD, FoodStatus.NO_FOOD); + return exchangeResult == FoodStatus.HAVE_FOOD; + } + +} diff --git a/src/main/java/org/labs/Programmer.java b/src/main/java/org/labs/Programmer.java new file mode 100644 index 0000000..894d1e0 --- /dev/null +++ b/src/main/java/org/labs/Programmer.java @@ -0,0 +1,95 @@ +package org.labs; + +import java.time.Duration; +import java.util.Collection; +import java.util.List; +import java.util.Random; +import java.util.concurrent.CompletionException; +import java.util.concurrent.atomic.AtomicLong; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class Programmer implements Runnable { + + private static final Logger logger = Logger.getLogger("Programmer"); + + private final Duration talkDuration; + + private final AtomicLong totalEat = new AtomicLong(); + + private final List waiters; + private final Random random; + private final Plate plate = new Plate(); + private final List spoons; + + public Programmer(Collection waiters, Spoon leftSpoon, Spoon rightSpoon, Random random, Duration talkDuration) { + if (waiters instanceof List waitersList) { + this.waiters = waitersList; + } else { + this.waiters = List.copyOf(waiters); + } + this.talkDuration = talkDuration; + spoons = leftSpoon.compareTo(rightSpoon) < 0 ? + List.of(leftSpoon, rightSpoon) : + List.of(rightSpoon, leftSpoon); + this.random = random; + } + + @Override + public void run() { + while (!Thread.interrupted()) { + eat(); + talk(); + } + } + + public long getTotalEat() { + return totalEat.get(); + } + + private void eat() { + switch (plate.getStatus()) { + case HAVE_FOOD -> { + synchronized (spoons.get(0)) { + synchronized (spoons.get(1)) { + boolean consumed = plate.consumeFood(); + if (consumed) { + totalEat.incrementAndGet(); + } + } + } + } + case NO_FOOD -> callWaiter(); + case NO_MORE_FOOD -> Thread.currentThread().interrupt(); + case AWAIT_FOOD -> {} + } + } + + private void talk() { + try { + Thread.sleep(talkDuration); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + private void callWaiter() { + var waiter = waiters.get(random.nextInt(waiters.size())); + plate.startAwaitFood(); + waiter.bringFood() + .thenAccept(this::receiveFood) + .exceptionally(e -> { + logger.log(Level.SEVERE, "Fail to bring food", e); + throw new CompletionException(e); + }); + } + + private void receiveFood(boolean isSuccess) { + if (isSuccess) { + plate.receiveFood(); + } else { + plate.noMoreFood(); + } + } + +} diff --git a/src/main/java/org/labs/Spoon.java b/src/main/java/org/labs/Spoon.java new file mode 100644 index 0000000..f7c4365 --- /dev/null +++ b/src/main/java/org/labs/Spoon.java @@ -0,0 +1,19 @@ +package org.labs; + +import java.util.concurrent.atomic.AtomicLong; + +public class Spoon implements Comparable { + + private static final AtomicLong currentSpoonId = new AtomicLong(); + + private final long id; + + public Spoon() { + this.id = currentSpoonId.getAndIncrement(); + } + + @Override + public int compareTo(Spoon o) { + return Long.compare(id, o.id); + } +} diff --git a/src/main/java/org/labs/Waiter.java b/src/main/java/org/labs/Waiter.java new file mode 100644 index 0000000..305f5c1 --- /dev/null +++ b/src/main/java/org/labs/Waiter.java @@ -0,0 +1,30 @@ +package org.labs; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicLong; + +public class Waiter { + + private final Kitchen kitchen; + private final AtomicLong deliveredFood = new AtomicLong(); + + public Waiter(Kitchen kitchen) { + this.kitchen = kitchen; + } + + public long getDeliveredFoodCount() { + return deliveredFood.get(); + } + + public CompletableFuture bringFood() { + return CompletableFuture + .supplyAsync(kitchen::cookFood) + .thenApply(food -> { + if (food) { + deliveredFood.incrementAndGet(); + } + return food; + }); + } + +} diff --git a/src/test/java/org/labs/BaseTests.java b/src/test/java/org/labs/BaseTests.java new file mode 100644 index 0000000..55000dc --- /dev/null +++ b/src/test/java/org/labs/BaseTests.java @@ -0,0 +1,37 @@ +package org.labs; + +import org.junit.jupiter.api.RepeatedTest; + +import java.time.Duration; + +public class BaseTests { + + @RepeatedTest(20) + public void baseTest() { + TestTable testTable = new TestTable(1_000, 3, 7, Duration.ZERO); + testTable.run(); + testTable.testResult(); + } + + @RepeatedTest(20) + public void nonZeroDurationTest() { + TestTable testTable = new TestTable(1_000, 3, 7, Duration.ofNanos(100)); + testTable.run(); + testTable.testResult(); + } + + @RepeatedTest(20) + public void manyProgrammerTest() { + TestTable testTable = new TestTable(1_000, 2, 100, Duration.ZERO); + testTable.run(); + testTable.testResult(); + } + + @RepeatedTest(20) + public void manyWaitersTest() { + TestTable testTable = new TestTable(1_000, 1000, 2, Duration.ZERO); + testTable.run(); + testTable.testResult(); + } + +} diff --git a/src/test/java/org/labs/TestTable.java b/src/test/java/org/labs/TestTable.java new file mode 100644 index 0000000..81cf724 --- /dev/null +++ b/src/test/java/org/labs/TestTable.java @@ -0,0 +1,68 @@ +package org.labs; + +import org.junit.jupiter.api.Assertions; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.stream.LongStream; + +public class TestTable { + + private final long foodCount; + + private final List waiters; + private final List spoons; + private final List programmers; + + public TestTable(long foodCount, long waiterCount, long programmerCount, Duration talkDuration) { + this.foodCount = foodCount; + Kitchen kitchen = new Kitchen(foodCount); + waiters = LongStream.range(0, waiterCount) + .mapToObj(num -> new Waiter(kitchen)) + .toList(); + spoons = LongStream.range(0, programmerCount) + .mapToObj(num -> new Spoon()) + .toList(); + programmers = LongStream.range(0, programmerCount) + .mapToObj(num -> new Programmer( + waiters, + spoons.get((int) (num % spoons.size())), + spoons.get((int) ((num + 1) % spoons.size())), + new Random(), + talkDuration + )) + .toList(); + } + + public void run() { + List threads = new ArrayList<>(); + for (Programmer prog : programmers) { + Thread t = new Thread(prog); + threads.add(t); + t.start(); + } + threads.forEach((t) -> { + try { + t.join(); + } catch (InterruptedException e) { + throw new RuntimeException("Main thread interrupted"); + } + }); + } + + public void testResult() { + Assertions.assertEquals( + foodCount, + programmers.stream().mapToLong(Programmer::getTotalEat).sum(), + "Programmers eat wrong count" + ); + Assertions.assertEquals( + foodCount, + waiters.stream().mapToLong(Waiter::getDeliveredFoodCount).sum(), + "Waiters deliver wrong count of food" + ); + } + +}