From d508aa6d6bb54a765a1a1206850ffc566f8d24ed Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Sun, 14 Sep 2025 15:24:05 +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 14feac4feabf2526079f6c82f1a3edc3f576e7f4 Mon Sep 17 00:00:00 2001 From: Vitaliy Emelyanov Date: Mon, 15 Sep 2025 20:27:11 +0300 Subject: [PATCH 2/2] =?UTF-8?q?=D0=91=D0=B0=D0=B7=D0=BE=D0=B2=D0=B0=D1=8F?= =?UTF-8?q?=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 2 + gradlew | 0 src/main/java/org/labs/Main.java | 9 +- src/main/java/org/labs/Programmer.java | 71 +++++++++++ src/main/java/org/labs/Restaurant.java | 40 +++++++ src/main/java/org/labs/Spoon.java | 25 ++++ src/main/java/org/labs/Table.java | 44 +++++++ src/main/java/org/labs/Waiter.java | 58 +++++++++ .../org/labs/RestaurantPerformanceTest.java | 112 ++++++++++++++++++ 9 files changed, 360 insertions(+), 1 deletion(-) mode change 100644 => 100755 gradlew create mode 100644 src/main/java/org/labs/Programmer.java create mode 100644 src/main/java/org/labs/Restaurant.java create mode 100644 src/main/java/org/labs/Spoon.java create mode 100644 src/main/java/org/labs/Table.java create mode 100644 src/main/java/org/labs/Waiter.java create mode 100644 src/test/java/org/labs/RestaurantPerformanceTest.java diff --git a/build.gradle.kts b/build.gradle.kts index bda0d97..ff24e6d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,6 +12,8 @@ repositories { dependencies { testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") + testImplementation("org.openjdk.jmh:jmh-core:1.37") + testImplementation("org.openjdk.jmh:jmh-generator-annprocess:1.37") } tasks.test { diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/src/main/java/org/labs/Main.java b/src/main/java/org/labs/Main.java index 9917247..6bf34b2 100644 --- a/src/main/java/org/labs/Main.java +++ b/src/main/java/org/labs/Main.java @@ -1,7 +1,14 @@ package org.labs; public class Main { + public static final boolean DEBUG = true; + public static void main(String[] args) { - System.out.println("Hello, World!"); + Restaurant restaurant = new Restaurant(100, 2, 1_000_000, false); + restaurant.start(); + restaurant.join(); + if (DEBUG) { + System.out.println("Нечестность = " + restaurant.getUnfairness()); + } } } \ 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..93ea0f7 --- /dev/null +++ b/src/main/java/org/labs/Programmer.java @@ -0,0 +1,71 @@ +package org.labs; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.atomic.AtomicInteger; + +public class Programmer extends Thread { + private static final AtomicInteger ID_CNT = new AtomicInteger(); + + private final BlockingQueue waiters; + private final Spoon first; + private final Spoon second; + private volatile boolean hasSoup; + private long ate = 0; + + public Programmer(BlockingQueue waiters, Spoon leftSpoon, Spoon rightSpoon) { + setName(Programmer.class.getName() + '.' + ID_CNT.incrementAndGet()); + setDaemon(true); + this.waiters = waiters; + this.hasSoup = true; + if (leftSpoon.compareTo(rightSpoon) > 0) { + this.first = rightSpoon; + this.second = leftSpoon; + } else { + this.first = leftSpoon; + this.second = rightSpoon; + } + } + + @Override + public void run() { + while (true) { + if (hasSoup) { + eat(); + } else { + requestMoreSoup(); + } + } + } + + public synchronized void addMoreSoup() { + this.hasSoup = true; + notify(); + } + + private void eat() { + first.lock(); + second.lock(); + hasSoup = false; + if (Main.DEBUG) { + ate++; + } + first.unlock(); + second.unlock(); + } + + private synchronized void requestMoreSoup() { + try { + this.waiters.take().requestSoup(this); + wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + public long getAte() { + if (!Main.DEBUG) { + throw new UnsupportedOperationException("Debug is not enabled!"); + } + return ate; + } +} diff --git a/src/main/java/org/labs/Restaurant.java b/src/main/java/org/labs/Restaurant.java new file mode 100644 index 0000000..e680fdb --- /dev/null +++ b/src/main/java/org/labs/Restaurant.java @@ -0,0 +1,40 @@ +package org.labs; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.atomic.AtomicInteger; + +public class Restaurant { + private final Table table; + private final AtomicInteger portionsLeft; + private final BlockingQueue waitersQueue; + + public Restaurant(int programmersCnt, int waitersCnt, int portions, boolean fairLock) { + this.portionsLeft = new AtomicInteger(portions - programmersCnt); + this.waitersQueue = new ArrayBlockingQueue<>(waitersCnt, fairLock); + for (int i = 0; i < waitersCnt; i++) { + try { + this.waitersQueue.put(new Waiter(this.portionsLeft, this.waitersQueue)); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + this.table = new Table(this.waitersQueue, programmersCnt, fairLock); + } + + public void start() { + for (Waiter waiter : waitersQueue) { + waiter.start(); + } + table.start(); + } + + public void join() { + while (portionsLeft.get() > 0) ; + } + + public double getUnfairness() { + return table.getUnfairness(); + } +} diff --git a/src/main/java/org/labs/Spoon.java b/src/main/java/org/labs/Spoon.java new file mode 100644 index 0000000..44acf7a --- /dev/null +++ b/src/main/java/org/labs/Spoon.java @@ -0,0 +1,25 @@ +package org.labs; + +import java.util.concurrent.locks.ReentrantLock; + +public class Spoon extends ReentrantLock implements Comparable { + private final int id; + + public Spoon(int id) { + this(id, true); + } + + public Spoon(int id, boolean fair) { + super(fair); + this.id = id; + } + + public int getId() { + return id; + } + + @Override + public int compareTo(Spoon o) { + return this.id - o.id; + } +} diff --git a/src/main/java/org/labs/Table.java b/src/main/java/org/labs/Table.java new file mode 100644 index 0000000..d7e8165 --- /dev/null +++ b/src/main/java/org/labs/Table.java @@ -0,0 +1,44 @@ +package org.labs; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingQueue; + +public class Table { + final private List programmers; + + public Table(BlockingQueue waiters, int programmersCnt, boolean fairLock) { + programmers = new ArrayList<>(programmersCnt); + List spoons = new ArrayList<>(programmersCnt); + for (int i = 0; i < programmersCnt; i++) { + if (i % 2 == 0) { + spoons.add(new Spoon(i / 2, fairLock)); + } else { + spoons.add(new Spoon(programmersCnt - (i / 2), fairLock)); + } + } + for (int i = 0; i < programmersCnt; i++) { + Spoon left = spoons.get(i); + Spoon right = spoons.get((i + 1) % programmersCnt); + programmers.add(new Programmer(waiters, left, right)); + } + } + + public void start() { + for (Programmer programmer : this.programmers) { + programmer.start(); + } + } + + public double getUnfairness() { + double mean = 0; + for (Programmer programmer: programmers) { + mean += ((double) programmer.getAte()) / programmers.size(); + } + double unfairness = 0; + for (Programmer programmer: programmers) { + unfairness += Math.abs(programmer.getAte() - mean) / (programmers.size()); + } + return unfairness / mean; + } +} diff --git a/src/main/java/org/labs/Waiter.java b/src/main/java/org/labs/Waiter.java new file mode 100644 index 0000000..144f0ef --- /dev/null +++ b/src/main/java/org/labs/Waiter.java @@ -0,0 +1,58 @@ +package org.labs; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Logger; + +public class Waiter extends Thread { + private final static Logger LOG = Logger.getLogger(Waiter.class.getName()); + private final static AtomicInteger ID_CNT = new AtomicInteger(); + private final BlockingQueue waitingProgrammers = new SynchronousQueue<>(); + private final AtomicInteger portionLeft; + private final BlockingQueue waiters; + private int delivered = 0; + + public Waiter(AtomicInteger portionLeft, BlockingQueue waiters) { + setName(Waiter.class.getName() + '.' + ID_CNT.incrementAndGet()); + this.portionLeft = portionLeft; + this.waiters = waiters; + } + + @Override + public void run() { + while (true) { + try { + Programmer programmer = waitingProgrammers.take(); + if (tryToTakeSoup()) { + programmer.addMoreSoup(); + if (Main.DEBUG) { + delivered++; + } + } else { + LOG.info("Официант покинул работу"); + return; + } + waiters.put(this); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + + public void requestSoup(Programmer programmer) throws InterruptedException { + waitingProgrammers.put(programmer); + } + + private boolean tryToTakeSoup() { + while (true) { + int currentPortionsLeft = portionLeft.get(); + if (currentPortionsLeft <= 0) { + return false; + } + if (portionLeft.compareAndSet(currentPortionsLeft, currentPortionsLeft - 1)) { + return true; + } + } + } +} diff --git a/src/test/java/org/labs/RestaurantPerformanceTest.java b/src/test/java/org/labs/RestaurantPerformanceTest.java new file mode 100644 index 0000000..617e70f --- /dev/null +++ b/src/test/java/org/labs/RestaurantPerformanceTest.java @@ -0,0 +1,112 @@ +package org.labs; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.junit.jupiter.params.provider.CsvSource; + +import java.util.logging.Level; +import java.util.logging.Logger; + + +@DisplayName("Restaurant Performance Tests") +public class RestaurantPerformanceTest { + + @BeforeAll + public static void disableLogging() { + Logger rootLogger = Logger.getLogger(""); + rootLogger.setLevel(Level.OFF); + } + + @ParameterizedTest + @ValueSource(ints = {2, 4, 8, 16, 32}) + @DisplayName("Тестирование производительности при различном количестве программистов") + public void testProgrammerCountPerformance(int programmersCnt) { + int waitersCnt = 2; + boolean fairLock = true; + + long executionTime = measurePerformance(programmersCnt, waitersCnt, fairLock); + + System.out.printf("Программистов: %d, Официантов: %d, Fair Lock: %s%n", programmersCnt, waitersCnt, fairLock); + System.out.printf("Время выполнения: %dмс%n", executionTime); + } + + @ParameterizedTest + @ValueSource(ints = {1, 2, 4, 8}) + @DisplayName("Тестирование производительности при различном количестве официантов") + public void testWaiterCountPerformance(int waitersCnt) { + int programmersCnt = 16; + boolean fairLock = true; + + long executionTime = measurePerformance(programmersCnt, waitersCnt, fairLock); + + System.out.printf("Программистов: %d, Официантов: %d, Fair Lock: %s%n", programmersCnt, waitersCnt, fairLock); + System.out.printf("Время выполнения: %dms%n", executionTime); + } + + @ParameterizedTest + @CsvSource({"4, 2, true", "4, 2, false", "8, 2, true", "8, 2, false", "16, 4, true", "16, 4, false"}) + @DisplayName("Тестирование производительности при различных типах блокировок") + public void testFairVsUnfairLockPerformance(int programmersCnt, int waitersCnt, boolean fairLock) { + long executionTime = measurePerformance(programmersCnt, waitersCnt, fairLock); + + System.out.printf("Программистов: %d, Официантов: %d, Fair Lock: %s%n", programmersCnt, waitersCnt, fairLock); + System.out.printf("Время выполнения: %dмс%n", executionTime); + } + + @Test + @DisplayName("Стресс-тест с высокой конкурентостью") + public void stressTest() { + int programmersCnt = 64; + int waitersCnt = 16; + boolean fairLock = true; + + long executionTime = measurePerformance(programmersCnt, waitersCnt, fairLock); + + System.out.printf("Стресс-тест - Программистов: %d, Официантов: %d%n", programmersCnt, waitersCnt); + System.out.printf("Время выполнения: %dms%n", executionTime); + } + + private long measurePerformance(int programmersCnt, int waitersCnt, boolean fairLock) { + final int warmupIterations = 2; + final int measurementIterations = 4; + + // Прогрев + System.out.printf("Прогрев (%d итераций)...%n", warmupIterations); + for (int i = 0; i < warmupIterations; i++) { + runSingleIteration(programmersCnt, waitersCnt, fairLock); + } + + // Измерения для усреднения + System.out.printf("Измерение производительности (%d итераций)...%n", measurementIterations); + long totalTime = 0; + for (int i = 0; i < measurementIterations; i++) { + long iterationTime = runSingleIteration(programmersCnt, waitersCnt, fairLock); + totalTime += iterationTime; + System.out.printf("\tИтерация %d: %dмс%n", i + 1, iterationTime); + } + + long averageTime = totalTime / measurementIterations; + System.out.printf("Среднее время: %d мс\n", averageTime); + + return averageTime; + } + + private long runSingleIteration(int programmersCnt, int waitersCnt, boolean fairLock) { + long startTime = System.currentTimeMillis(); + + Restaurant restaurant = new Restaurant(programmersCnt, waitersCnt, 100_000, fairLock); + restaurant.start(); + restaurant.join(); + + System.out.println("Нечестность = " + restaurant.getUnfairness()); + assert !fairLock || restaurant.getUnfairness() < 0.1; + long endTime = System.currentTimeMillis(); + + return endTime - startTime; + } + +}