From 26e857b199d34ce85c781098568896c21f85e3e8 Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Sun, 14 Sep 2025 11:54:47 +0000 Subject: [PATCH 01/12] 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 59aa80ab25f5ca49861afe24f00efd4ee8b05199 Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Thu, 18 Sep 2025 16:27:53 +0300 Subject: [PATCH 02/12] add unfair ordered lock solution --- src/main/java/org/labs/common/Config.java | 10 ++ .../java/org/labs/common/IdGenerator.java | 11 +++ src/main/java/org/labs/common/Spoon.java | 13 +++ src/main/java/org/labs/common/Statistic.java | 39 ++++++++ .../org/labs/orderedlocks/DiningStudents.java | 91 +++++++++++++++++++ .../java/org/labs/orderedlocks/Kitchen.java | 32 +++++++ .../java/org/labs/orderedlocks/Student.java | 66 ++++++++++++++ .../java/org/labs/orderedlocks/Waiter.java | 53 +++++++++++ 8 files changed, 315 insertions(+) create mode 100644 src/main/java/org/labs/common/Config.java create mode 100644 src/main/java/org/labs/common/IdGenerator.java create mode 100644 src/main/java/org/labs/common/Spoon.java create mode 100644 src/main/java/org/labs/common/Statistic.java create mode 100644 src/main/java/org/labs/orderedlocks/DiningStudents.java create mode 100644 src/main/java/org/labs/orderedlocks/Kitchen.java create mode 100644 src/main/java/org/labs/orderedlocks/Student.java create mode 100644 src/main/java/org/labs/orderedlocks/Waiter.java diff --git a/src/main/java/org/labs/common/Config.java b/src/main/java/org/labs/common/Config.java new file mode 100644 index 0000000..a98d768 --- /dev/null +++ b/src/main/java/org/labs/common/Config.java @@ -0,0 +1,10 @@ +package org.labs.common; + +public class Config { + public static final int NUMBER_OF_STUDENTS = 7; + public static final int NUMBER_OF_SOUP = 1_000_000; + public static final int NUMBER_OF_WAITERS = 2; + + public static final long TIME_TO_EAT_SOUP_MS = 0; + public static final long TIME_TO_SPEAK_MS = 1; +} diff --git a/src/main/java/org/labs/common/IdGenerator.java b/src/main/java/org/labs/common/IdGenerator.java new file mode 100644 index 0000000..10ab726 --- /dev/null +++ b/src/main/java/org/labs/common/IdGenerator.java @@ -0,0 +1,11 @@ +package org.labs.common; + +import java.util.concurrent.atomic.AtomicInteger; + +public class IdGenerator { + private final AtomicInteger id = new AtomicInteger(0); + + public int getId() { + return id.getAndIncrement(); + } +} diff --git a/src/main/java/org/labs/common/Spoon.java b/src/main/java/org/labs/common/Spoon.java new file mode 100644 index 0000000..c7c6ace --- /dev/null +++ b/src/main/java/org/labs/common/Spoon.java @@ -0,0 +1,13 @@ +package org.labs.common; + +public class Spoon { + private final Integer id; + + public Spoon(int id) { + this.id = id; + } + + public Integer getId() { + return id; + } +} diff --git a/src/main/java/org/labs/common/Statistic.java b/src/main/java/org/labs/common/Statistic.java new file mode 100644 index 0000000..bcfbe9d --- /dev/null +++ b/src/main/java/org/labs/common/Statistic.java @@ -0,0 +1,39 @@ +package org.labs.common; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +public class Statistic { + private final List studentStatistic; + private final List waiterStatistic; + + public Statistic(Integer studentsCount, Integer waitersCount) { + this.studentStatistic = new ArrayList<>(studentsCount); + this.waiterStatistic = new ArrayList<>(waitersCount); + + for (int i = 0; i < studentsCount; ++i) { + this.studentStatistic.add(new AtomicInteger(0)); + } + + for (int i = 0; i < waitersCount; ++i) { + this.waiterStatistic.add(new AtomicInteger(0)); + } + } + + public void addStudentStatistic(int studentId) { + studentStatistic.get(studentId).incrementAndGet(); + } + + public void addWaiterStatistic(int waiterId) { + waiterStatistic.get(waiterId).incrementAndGet(); + } + + public Integer getStudentStatistic(int studentId) { + return studentStatistic.get(studentId).get(); + } + + public Integer getWaiterStatistic(int waiterId) { + return waiterStatistic.get(waiterId).get(); + } +} diff --git a/src/main/java/org/labs/orderedlocks/DiningStudents.java b/src/main/java/org/labs/orderedlocks/DiningStudents.java new file mode 100644 index 0000000..a8cbf94 --- /dev/null +++ b/src/main/java/org/labs/orderedlocks/DiningStudents.java @@ -0,0 +1,91 @@ +package org.labs.orderedlocks; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletableFuture; +import org.labs.common.Config; +import org.labs.common.Spoon; +import org.labs.common.Statistic; +import org.labs.orderedlocks.Kitchen.SoupOrderStatus; + +public class DiningStudents { + + public static void main(String[] args) { + var statistic = new Statistic(Config.NUMBER_OF_STUDENTS, Config.NUMBER_OF_WAITERS); + + List spoons = new ArrayList<>(Config.NUMBER_OF_STUDENTS); + for (int i = 0; i < Config.NUMBER_OF_STUDENTS; ++i) { + spoons.add(new Spoon(i)); + } + + Kitchen kitchen = new Kitchen(); + + BlockingQueue> orders = new ArrayBlockingQueue<>( + Config.NUMBER_OF_SOUP + Config.NUMBER_OF_STUDENTS + 1, + false + ); + + List waiters = new ArrayList<>(Config.NUMBER_OF_WAITERS); + for (int i = 0; i < Config.NUMBER_OF_WAITERS; ++i) { + var waiterRunnable = new Waiter(i, orders, kitchen, statistic); + var waiterThread = new Thread(waiterRunnable); + waiterThread.start(); + waiters.add(waiterThread); + } + + List students = new ArrayList<>(Config.NUMBER_OF_STUDENTS); + for (int i = 0; i < Config.NUMBER_OF_STUDENTS; ++i) { + // debug i == zero case + int leftSpoonId = i != Config.NUMBER_OF_STUDENTS - 1 ? + i == 0 ? Config.NUMBER_OF_STUDENTS - 1 : i - 1 : i; + int rightSpoonId = i != Config.NUMBER_OF_STUDENTS - 1 ? i : i - 1; + var studentRunnable = new Student( + i, + statistic, + spoons.get(leftSpoonId), + spoons.get(rightSpoonId), + orders + ); + var studentThread = new Thread(studentRunnable); + studentThread.start(); + students.add(studentThread); + } + + students.forEach((thread -> { + try { + thread.join(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }) + ); + waiters.forEach(Thread::interrupt); + + printStatistic(statistic); + } + + private static void printStatistic(Statistic statistic) { + StringBuilder sb = new StringBuilder("Statistic:\nStudents: ["); + + for (int i = 0; i < Config.NUMBER_OF_STUDENTS; ++i) { + sb.append(statistic.getStudentStatistic(i)); + if (i != Config.NUMBER_OF_STUDENTS - 1) { + sb.append(", "); + } + } + + sb.append("]\nWaiters: ["); + + for (int i = 0; i < Config.NUMBER_OF_WAITERS; ++i) { + sb.append(statistic.getWaiterStatistic(i)); + if (i != Config.NUMBER_OF_WAITERS - 1) { + sb.append(", "); + } + } + sb.append("]"); + + System.out.println(sb); + } +} diff --git a/src/main/java/org/labs/orderedlocks/Kitchen.java b/src/main/java/org/labs/orderedlocks/Kitchen.java new file mode 100644 index 0000000..b402213 --- /dev/null +++ b/src/main/java/org/labs/orderedlocks/Kitchen.java @@ -0,0 +1,32 @@ +package org.labs.orderedlocks; + +import java.util.concurrent.atomic.AtomicInteger; +import org.labs.common.Config; + +public class Kitchen { + + private final AtomicInteger soupCount = new AtomicInteger(Config.NUMBER_OF_SOUP); + + public enum SoupOrderStatus { + OK, + OUT_OF_SOUP, + } + + public SoupOrderStatus getSoup() { + Integer currentSoupCount; + + do { + currentSoupCount = soupCount.get(); + + if (currentSoupCount.equals(0)) { + System.out.println("Kitchen " + currentSoupCount); + return SoupOrderStatus.OUT_OF_SOUP; + } + } while (!soupCount.compareAndSet(currentSoupCount, currentSoupCount - 1)); + + if ((currentSoupCount % 10000) == 0) { + System.out.println("Kitchen: " + currentSoupCount); + } + return SoupOrderStatus.OK; + } +} diff --git a/src/main/java/org/labs/orderedlocks/Student.java b/src/main/java/org/labs/orderedlocks/Student.java new file mode 100644 index 0000000..550cf79 --- /dev/null +++ b/src/main/java/org/labs/orderedlocks/Student.java @@ -0,0 +1,66 @@ +package org.labs.orderedlocks; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import org.labs.common.Config; +import org.labs.common.Spoon; +import org.labs.common.Statistic; +import org.labs.orderedlocks.Kitchen.SoupOrderStatus; + +public class Student implements Runnable { + + private final Integer id; + private final Statistic statistic; + private final Spoon leftSpoon; + private final Spoon rightSpoon; + private final BlockingQueue> orders; + + public Student(Integer id, Statistic statistic, Spoon leftSpoon, Spoon rightSpoon, BlockingQueue> orders) { + this.id = id; + this.statistic = statistic; + this.leftSpoon = leftSpoon; + this.rightSpoon = rightSpoon; + this.orders = orders; + } + + @Override + public void run() { + var name = Thread.currentThread().getName(); + try { + while (true) { + try { + speak(); + + synchronized (leftSpoon) { + System.out.println(name + " took spoon with id: " + leftSpoon.getId()); + synchronized (rightSpoon) { + System.out.println(name + " took spoon with id: " + rightSpoon.getId()); + CompletableFuture order = new CompletableFuture<>(); + orders.put(order); + + var orderStatus = order.get(); + + if (orderStatus == SoupOrderStatus.OUT_OF_SOUP) { + return; + } + + Thread.sleep(Config.TIME_TO_EAT_SOUP_MS); + statistic.addStudentStatistic(id); + } + } + // todo execution ex + } catch (ExecutionException e) { + Thread.currentThread().interrupt(); + return; + } + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + private void speak() throws InterruptedException { + Thread.sleep(Config.TIME_TO_SPEAK_MS); + } +} diff --git a/src/main/java/org/labs/orderedlocks/Waiter.java b/src/main/java/org/labs/orderedlocks/Waiter.java new file mode 100644 index 0000000..8b3865d --- /dev/null +++ b/src/main/java/org/labs/orderedlocks/Waiter.java @@ -0,0 +1,53 @@ +package org.labs.orderedlocks; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletableFuture; +import org.labs.common.Statistic; +import org.labs.orderedlocks.Kitchen.SoupOrderStatus; + +public class Waiter implements Runnable { + + private final Integer id; + private final BlockingQueue> orders; + private final Kitchen kitchen; + private final Statistic statistic; + + public Waiter(Integer id, BlockingQueue> orders, Kitchen kitchen, Statistic statistic) { + this.id = id; + this.orders = orders; + this.kitchen = kitchen; + this.statistic = statistic; + } + + // Почитать про реордеринг условия в while + @Override + public void run() { + try { + while (true) { + CompletableFuture order = null; + try { + order = orders.take(); + SoupOrderStatus orderStatus = kitchen.getSoup(); + + order.complete(orderStatus); + + if (orderStatus != SoupOrderStatus.OUT_OF_SOUP) { + statistic.addWaiterStatistic(id); + } + } catch (InterruptedException e) { + if (order != null) { + order.completeExceptionally(e); + } + Thread.currentThread().interrupt(); + throw e; + } finally { + if (order != null) { + order.completeExceptionally(new RuntimeException("bla-bla")); + } + } + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } +} From fff324195dab3dd195463ce1110059645aac27ee Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Sun, 21 Sep 2025 15:44:39 +0300 Subject: [PATCH 03/12] +x on gradlew --- gradlew | 0 src/main/java/org/labs/common/Config.java | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 gradlew diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/src/main/java/org/labs/common/Config.java b/src/main/java/org/labs/common/Config.java index a98d768..68b7e29 100644 --- a/src/main/java/org/labs/common/Config.java +++ b/src/main/java/org/labs/common/Config.java @@ -6,5 +6,5 @@ public class Config { public static final int NUMBER_OF_WAITERS = 2; public static final long TIME_TO_EAT_SOUP_MS = 0; - public static final long TIME_TO_SPEAK_MS = 1; + public static final long TIME_TO_SPEAK_MS = 0; } From f6b51562cb9c2deeed235d2cdb732aedb3c2c183 Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Sun, 21 Sep 2025 15:51:39 +0300 Subject: [PATCH 04/12] add logging dependency --- build.gradle.kts | 3 +++ src/main/java/org/labs/Main.java | 7 ------- src/main/resources/logback.xml | 11 +++++++++++ 3 files changed, 14 insertions(+), 7 deletions(-) delete mode 100644 src/main/java/org/labs/Main.java create mode 100644 src/main/resources/logback.xml diff --git a/build.gradle.kts b/build.gradle.kts index bda0d97..90c5c85 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,6 +10,9 @@ repositories { } dependencies { + implementation("org.slf4j:slf4j-api:2.0.16") + implementation("ch.qos.logback:logback-classic:1.5.13") + 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 deleted file mode 100644 index 9917247..0000000 --- a/src/main/java/org/labs/Main.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.labs; - -public class Main { - public static void main(String[] args) { - System.out.println("Hello, World!"); - } -} \ No newline at end of file diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..aedad0c --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,11 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + From 5f300ef901d4219ec712b8e95567d73d8be47ff2 Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Sun, 21 Sep 2025 16:25:05 +0300 Subject: [PATCH 05/12] minor refactoring --- src/main/java/org/labs/common/Config.java | 3 + .../java/org/labs/common/IdGenerator.java | 11 --- src/main/java/org/labs/common/Statistic.java | 30 +++++- .../org/labs/orderedlocks/DiningStudents.java | 93 ++++++++++--------- .../java/org/labs/orderedlocks/Kitchen.java | 9 +- .../java/org/labs/orderedlocks/Student.java | 32 ++++--- .../java/org/labs/orderedlocks/Waiter.java | 14 ++- 7 files changed, 107 insertions(+), 85 deletions(-) delete mode 100644 src/main/java/org/labs/common/IdGenerator.java diff --git a/src/main/java/org/labs/common/Config.java b/src/main/java/org/labs/common/Config.java index 68b7e29..a2d4f30 100644 --- a/src/main/java/org/labs/common/Config.java +++ b/src/main/java/org/labs/common/Config.java @@ -7,4 +7,7 @@ public class Config { public static final long TIME_TO_EAT_SOUP_MS = 0; public static final long TIME_TO_SPEAK_MS = 0; + + private Config() { + } } diff --git a/src/main/java/org/labs/common/IdGenerator.java b/src/main/java/org/labs/common/IdGenerator.java deleted file mode 100644 index 10ab726..0000000 --- a/src/main/java/org/labs/common/IdGenerator.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.labs.common; - -import java.util.concurrent.atomic.AtomicInteger; - -public class IdGenerator { - private final AtomicInteger id = new AtomicInteger(0); - - public int getId() { - return id.getAndIncrement(); - } -} diff --git a/src/main/java/org/labs/common/Statistic.java b/src/main/java/org/labs/common/Statistic.java index bcfbe9d..779644c 100644 --- a/src/main/java/org/labs/common/Statistic.java +++ b/src/main/java/org/labs/common/Statistic.java @@ -3,8 +3,13 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class Statistic { + + private static final Logger logger = LoggerFactory.getLogger(Statistic.class); + private final List studentStatistic; private final List waiterStatistic; @@ -29,11 +34,26 @@ public void addWaiterStatistic(int waiterId) { waiterStatistic.get(waiterId).incrementAndGet(); } - public Integer getStudentStatistic(int studentId) { - return studentStatistic.get(studentId).get(); - } + public void printStatistic() { + StringBuilder sb = new StringBuilder("Statistic:\nStudents: ["); + + for (int i = 0; i < Config.NUMBER_OF_STUDENTS; ++i) { + sb.append(studentStatistic.get(i).get()); + if (i != Config.NUMBER_OF_STUDENTS - 1) { + sb.append(", "); + } + } + + sb.append("]\nWaiters: ["); + + for (int i = 0; i < Config.NUMBER_OF_WAITERS; ++i) { + sb.append(waiterStatistic.get(i).get()); + if (i != Config.NUMBER_OF_WAITERS - 1) { + sb.append(", "); + } + } + sb.append("]"); - public Integer getWaiterStatistic(int waiterId) { - return waiterStatistic.get(waiterId).get(); + logger.info(sb.toString()); } } diff --git a/src/main/java/org/labs/orderedlocks/DiningStudents.java b/src/main/java/org/labs/orderedlocks/DiningStudents.java index a8cbf94..92e9343 100644 --- a/src/main/java/org/labs/orderedlocks/DiningStudents.java +++ b/src/main/java/org/labs/orderedlocks/DiningStudents.java @@ -15,10 +15,7 @@ public class DiningStudents { public static void main(String[] args) { var statistic = new Statistic(Config.NUMBER_OF_STUDENTS, Config.NUMBER_OF_WAITERS); - List spoons = new ArrayList<>(Config.NUMBER_OF_STUDENTS); - for (int i = 0; i < Config.NUMBER_OF_STUDENTS; ++i) { - spoons.add(new Spoon(i)); - } + List spoons = createSpoons(Config.NUMBER_OF_STUDENTS); Kitchen kitchen = new Kitchen(); @@ -27,31 +24,8 @@ public static void main(String[] args) { false ); - List waiters = new ArrayList<>(Config.NUMBER_OF_WAITERS); - for (int i = 0; i < Config.NUMBER_OF_WAITERS; ++i) { - var waiterRunnable = new Waiter(i, orders, kitchen, statistic); - var waiterThread = new Thread(waiterRunnable); - waiterThread.start(); - waiters.add(waiterThread); - } - - List students = new ArrayList<>(Config.NUMBER_OF_STUDENTS); - for (int i = 0; i < Config.NUMBER_OF_STUDENTS; ++i) { - // debug i == zero case - int leftSpoonId = i != Config.NUMBER_OF_STUDENTS - 1 ? - i == 0 ? Config.NUMBER_OF_STUDENTS - 1 : i - 1 : i; - int rightSpoonId = i != Config.NUMBER_OF_STUDENTS - 1 ? i : i - 1; - var studentRunnable = new Student( - i, - statistic, - spoons.get(leftSpoonId), - spoons.get(rightSpoonId), - orders - ); - var studentThread = new Thread(studentRunnable); - studentThread.start(); - students.add(studentThread); - } + List waiters = createAndStartWaiters(Config.NUMBER_OF_WAITERS, orders, kitchen, statistic); + List students = createAndStartStudents(Config.NUMBER_OF_STUDENTS, spoons, statistic, orders); students.forEach((thread -> { try { @@ -63,29 +37,56 @@ public static void main(String[] args) { ); waiters.forEach(Thread::interrupt); - printStatistic(statistic); + statistic.printStatistic(); } - private static void printStatistic(Statistic statistic) { - StringBuilder sb = new StringBuilder("Statistic:\nStudents: ["); - + private static List createSpoons(int numberOfSpoons) { + List spoons = new ArrayList<>(numberOfSpoons); for (int i = 0; i < Config.NUMBER_OF_STUDENTS; ++i) { - sb.append(statistic.getStudentStatistic(i)); - if (i != Config.NUMBER_OF_STUDENTS - 1) { - sb.append(", "); - } + spoons.add(new Spoon(i)); } + return spoons; + } - sb.append("]\nWaiters: ["); - - for (int i = 0; i < Config.NUMBER_OF_WAITERS; ++i) { - sb.append(statistic.getWaiterStatistic(i)); - if (i != Config.NUMBER_OF_WAITERS - 1) { - sb.append(", "); - } + private static List createAndStartWaiters( + int numberOfWaiters, + BlockingQueue> orders, + Kitchen kitchen, + Statistic statistic + ) { + List waiters = new ArrayList<>(numberOfWaiters); + for (int i = 0; i < numberOfWaiters; ++i) { + var waiterRunnable = new Waiter(i, orders, kitchen, statistic); + var waiterThread = new Thread(waiterRunnable); + waiterThread.start(); + waiters.add(waiterThread); } - sb.append("]"); + return waiters; + } - System.out.println(sb); + private static List createAndStartStudents( + int numberOfStudents, + List spoons, + Statistic statistic, + BlockingQueue> orders + ) { + List students = new ArrayList<>(numberOfStudents); + for (int i = 0; i < numberOfStudents; ++i) { + // all students except for the last should take left spoon first + int firstSpoonId = i != Config.NUMBER_OF_STUDENTS - 1 ? + i == 0 ? Config.NUMBER_OF_STUDENTS - 1 : i - 1 : i; + int secondSpoonId = i != Config.NUMBER_OF_STUDENTS - 1 ? i : i - 1; + var studentRunnable = new Student( + i, + statistic, + spoons.get(firstSpoonId), + spoons.get(secondSpoonId), + orders + ); + var studentThread = new Thread(studentRunnable); + studentThread.start(); + students.add(studentThread); + } + return students; } } diff --git a/src/main/java/org/labs/orderedlocks/Kitchen.java b/src/main/java/org/labs/orderedlocks/Kitchen.java index b402213..b5bb8f7 100644 --- a/src/main/java/org/labs/orderedlocks/Kitchen.java +++ b/src/main/java/org/labs/orderedlocks/Kitchen.java @@ -2,9 +2,12 @@ import java.util.concurrent.atomic.AtomicInteger; import org.labs.common.Config; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class Kitchen { + private static final Logger logger = LoggerFactory.getLogger(Kitchen.class); private final AtomicInteger soupCount = new AtomicInteger(Config.NUMBER_OF_SOUP); public enum SoupOrderStatus { @@ -19,13 +22,13 @@ public SoupOrderStatus getSoup() { currentSoupCount = soupCount.get(); if (currentSoupCount.equals(0)) { - System.out.println("Kitchen " + currentSoupCount); + logger.info("Kitchen is out of soup"); return SoupOrderStatus.OUT_OF_SOUP; } } while (!soupCount.compareAndSet(currentSoupCount, currentSoupCount - 1)); - if ((currentSoupCount % 10000) == 0) { - System.out.println("Kitchen: " + currentSoupCount); + if (currentSoupCount % 10_000 == 0) { + logger.debug("Kitchen soup count {}", currentSoupCount); } return SoupOrderStatus.OK; } diff --git a/src/main/java/org/labs/orderedlocks/Student.java b/src/main/java/org/labs/orderedlocks/Student.java index 550cf79..27e522b 100644 --- a/src/main/java/org/labs/orderedlocks/Student.java +++ b/src/main/java/org/labs/orderedlocks/Student.java @@ -7,55 +7,63 @@ import org.labs.common.Spoon; import org.labs.common.Statistic; import org.labs.orderedlocks.Kitchen.SoupOrderStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class Student implements Runnable { + private static final Logger logger = LoggerFactory.getLogger(Student.class); + private final Integer id; private final Statistic statistic; - private final Spoon leftSpoon; - private final Spoon rightSpoon; + private final Spoon firstSpoon; + private final Spoon secondSpoon; private final BlockingQueue> orders; - public Student(Integer id, Statistic statistic, Spoon leftSpoon, Spoon rightSpoon, BlockingQueue> orders) { + public Student(Integer id, Statistic statistic, Spoon firstSpoon, Spoon secondSpoon, BlockingQueue> orders) { this.id = id; this.statistic = statistic; - this.leftSpoon = leftSpoon; - this.rightSpoon = rightSpoon; + this.firstSpoon = firstSpoon; + this.secondSpoon = secondSpoon; this.orders = orders; } @Override public void run() { - var name = Thread.currentThread().getName(); try { while (true) { try { speak(); - synchronized (leftSpoon) { - System.out.println(name + " took spoon with id: " + leftSpoon.getId()); - synchronized (rightSpoon) { - System.out.println(name + " took spoon with id: " + rightSpoon.getId()); + synchronized (firstSpoon) { + logger.debug("Student {} took spoon with id: {}", id, firstSpoon.getId()); + synchronized (secondSpoon) { + logger.debug("Student {} took spoon with id: {}", id, secondSpoon.getId()); + CompletableFuture order = new CompletableFuture<>(); orders.put(order); var orderStatus = order.get(); if (orderStatus == SoupOrderStatus.OUT_OF_SOUP) { + logger.info("no more food for {}, leaving restaurant", id); return; } Thread.sleep(Config.TIME_TO_EAT_SOUP_MS); statistic.addStudentStatistic(id); } + logger.debug("Student {} put down spoon with id: {}", id, secondSpoon.getId()); } - // todo execution ex + logger.debug("Student {} put down spoon with id: {}", id, firstSpoon.getId()); } catch (ExecutionException e) { - Thread.currentThread().interrupt(); + // should not happen + logger.error("Student {} received error, stopping", id, e); return; } } } catch (InterruptedException e) { + logger.warn("Student {} was interrupted", id); Thread.currentThread().interrupt(); } } diff --git a/src/main/java/org/labs/orderedlocks/Waiter.java b/src/main/java/org/labs/orderedlocks/Waiter.java index 8b3865d..53e1ebb 100644 --- a/src/main/java/org/labs/orderedlocks/Waiter.java +++ b/src/main/java/org/labs/orderedlocks/Waiter.java @@ -4,9 +4,13 @@ import java.util.concurrent.CompletableFuture; import org.labs.common.Statistic; import org.labs.orderedlocks.Kitchen.SoupOrderStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class Waiter implements Runnable { + private static final Logger logger = LoggerFactory.getLogger(Waiter.class); + private final Integer id; private final BlockingQueue> orders; private final Kitchen kitchen; @@ -19,7 +23,6 @@ public Waiter(Integer id, BlockingQueue> orde this.statistic = statistic; } - // Почитать про реордеринг условия в while @Override public void run() { try { @@ -34,19 +37,14 @@ public void run() { if (orderStatus != SoupOrderStatus.OUT_OF_SOUP) { statistic.addWaiterStatistic(id); } - } catch (InterruptedException e) { - if (order != null) { - order.completeExceptionally(e); - } - Thread.currentThread().interrupt(); - throw e; } finally { if (order != null) { - order.completeExceptionally(new RuntimeException("bla-bla")); + order.complete(SoupOrderStatus.OUT_OF_SOUP); } } } } catch (InterruptedException e) { + logger.info("Waiter {} was interrupted", id); Thread.currentThread().interrupt(); } } From 5c4be7d7d0fca24bed452be3cb0d8ad702477545 Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Sun, 21 Sep 2025 17:17:00 +0300 Subject: [PATCH 06/12] rewrite with locks, add time --- src/main/java/org/labs/common/Config.java | 2 ++ src/main/java/org/labs/common/Spoon.java | 12 +++++++++ src/main/java/org/labs/common/Statistic.java | 4 +-- .../org/labs/orderedlocks/DiningStudents.java | 19 +++++++++++--- .../java/org/labs/orderedlocks/Kitchen.java | 6 ++--- .../java/org/labs/orderedlocks/Student.java | 26 ++++++++++++------- .../java/org/labs/orderedlocks/Waiter.java | 4 +-- 7 files changed, 53 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/labs/common/Config.java b/src/main/java/org/labs/common/Config.java index a2d4f30..8397f38 100644 --- a/src/main/java/org/labs/common/Config.java +++ b/src/main/java/org/labs/common/Config.java @@ -8,6 +8,8 @@ public class Config { public static final long TIME_TO_EAT_SOUP_MS = 0; public static final long TIME_TO_SPEAK_MS = 0; + public static final boolean FAIR_IF_POSSIBLE = true; + private Config() { } } diff --git a/src/main/java/org/labs/common/Spoon.java b/src/main/java/org/labs/common/Spoon.java index c7c6ace..88965aa 100644 --- a/src/main/java/org/labs/common/Spoon.java +++ b/src/main/java/org/labs/common/Spoon.java @@ -1,7 +1,11 @@ package org.labs.common; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + public class Spoon { private final Integer id; + private final Lock lock = new ReentrantLock(Config.FAIR_IF_POSSIBLE); public Spoon(int id) { this.id = id; @@ -10,4 +14,12 @@ public Spoon(int id) { public Integer getId() { return id; } + + public void lock() { + lock.lock(); + } + + public void unlock() { + lock.unlock(); + } } diff --git a/src/main/java/org/labs/common/Statistic.java b/src/main/java/org/labs/common/Statistic.java index 779644c..75de9f1 100644 --- a/src/main/java/org/labs/common/Statistic.java +++ b/src/main/java/org/labs/common/Statistic.java @@ -8,7 +8,7 @@ public class Statistic { - private static final Logger logger = LoggerFactory.getLogger(Statistic.class); + private static final Logger log = LoggerFactory.getLogger(Statistic.class); private final List studentStatistic; private final List waiterStatistic; @@ -54,6 +54,6 @@ public void printStatistic() { } sb.append("]"); - logger.info(sb.toString()); + log.info(sb.toString()); } } diff --git a/src/main/java/org/labs/orderedlocks/DiningStudents.java b/src/main/java/org/labs/orderedlocks/DiningStudents.java index 92e9343..e4c7631 100644 --- a/src/main/java/org/labs/orderedlocks/DiningStudents.java +++ b/src/main/java/org/labs/orderedlocks/DiningStudents.java @@ -5,14 +5,19 @@ import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; import org.labs.common.Config; import org.labs.common.Spoon; import org.labs.common.Statistic; import org.labs.orderedlocks.Kitchen.SoupOrderStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class DiningStudents { - public static void main(String[] args) { + private static final Logger log = LoggerFactory.getLogger(DiningStudents.class); + + public static void main(String[] args) throws InterruptedException { var statistic = new Statistic(Config.NUMBER_OF_STUDENTS, Config.NUMBER_OF_WAITERS); List spoons = createSpoons(Config.NUMBER_OF_STUDENTS); @@ -21,10 +26,12 @@ public static void main(String[] args) { BlockingQueue> orders = new ArrayBlockingQueue<>( Config.NUMBER_OF_SOUP + Config.NUMBER_OF_STUDENTS + 1, - false + Config.FAIR_IF_POSSIBLE ); List waiters = createAndStartWaiters(Config.NUMBER_OF_WAITERS, orders, kitchen, statistic); + + var startTime = System.nanoTime(); List students = createAndStartStudents(Config.NUMBER_OF_STUDENTS, spoons, statistic, orders); students.forEach((thread -> { @@ -35,9 +42,15 @@ public static void main(String[] args) { } }) ); - waiters.forEach(Thread::interrupt); + var endTime = System.nanoTime(); + + for (var waiter : waiters) { + waiter.interrupt(); + waiter.join(); + } statistic.printStatistic(); + log.info("Time: {} ms", TimeUnit.NANOSECONDS.toMillis(endTime - startTime)); } private static List createSpoons(int numberOfSpoons) { diff --git a/src/main/java/org/labs/orderedlocks/Kitchen.java b/src/main/java/org/labs/orderedlocks/Kitchen.java index b5bb8f7..8497cb4 100644 --- a/src/main/java/org/labs/orderedlocks/Kitchen.java +++ b/src/main/java/org/labs/orderedlocks/Kitchen.java @@ -7,7 +7,7 @@ public class Kitchen { - private static final Logger logger = LoggerFactory.getLogger(Kitchen.class); + private static final Logger log = LoggerFactory.getLogger(Kitchen.class); private final AtomicInteger soupCount = new AtomicInteger(Config.NUMBER_OF_SOUP); public enum SoupOrderStatus { @@ -22,13 +22,13 @@ public SoupOrderStatus getSoup() { currentSoupCount = soupCount.get(); if (currentSoupCount.equals(0)) { - logger.info("Kitchen is out of soup"); + log.info("Kitchen is out of soup"); return SoupOrderStatus.OUT_OF_SOUP; } } while (!soupCount.compareAndSet(currentSoupCount, currentSoupCount - 1)); if (currentSoupCount % 10_000 == 0) { - logger.debug("Kitchen soup count {}", currentSoupCount); + log.debug("Kitchen soup count {}", currentSoupCount); } return SoupOrderStatus.OK; } diff --git a/src/main/java/org/labs/orderedlocks/Student.java b/src/main/java/org/labs/orderedlocks/Student.java index 27e522b..56100cb 100644 --- a/src/main/java/org/labs/orderedlocks/Student.java +++ b/src/main/java/org/labs/orderedlocks/Student.java @@ -12,7 +12,7 @@ public class Student implements Runnable { - private static final Logger logger = LoggerFactory.getLogger(Student.class); + private static final Logger log = LoggerFactory.getLogger(Student.class); private final Integer id; private final Statistic statistic; @@ -35,10 +35,12 @@ public void run() { try { speak(); - synchronized (firstSpoon) { - logger.debug("Student {} took spoon with id: {}", id, firstSpoon.getId()); - synchronized (secondSpoon) { - logger.debug("Student {} took spoon with id: {}", id, secondSpoon.getId()); + firstSpoon.lock(); + try { + log.debug("Student {} took spoon with id: {}", id, firstSpoon.getId()); + secondSpoon.lock(); + try { + log.debug("Student {} took spoon with id: {}", id, secondSpoon.getId()); CompletableFuture order = new CompletableFuture<>(); orders.put(order); @@ -46,24 +48,28 @@ public void run() { var orderStatus = order.get(); if (orderStatus == SoupOrderStatus.OUT_OF_SOUP) { - logger.info("no more food for {}, leaving restaurant", id); + log.info("no more food for {}, leaving restaurant", id); return; } Thread.sleep(Config.TIME_TO_EAT_SOUP_MS); statistic.addStudentStatistic(id); + } finally { + secondSpoon.unlock(); + log.debug("Student {} put down spoon with id: {}", id, secondSpoon.getId()); } - logger.debug("Student {} put down spoon with id: {}", id, secondSpoon.getId()); + } finally { + firstSpoon.unlock(); + log.debug("Student {} put down spoon with id: {}", id, firstSpoon.getId()); } - logger.debug("Student {} put down spoon with id: {}", id, firstSpoon.getId()); } catch (ExecutionException e) { // should not happen - logger.error("Student {} received error, stopping", id, e); + log.error("Student {} received error, stopping", id, e); return; } } } catch (InterruptedException e) { - logger.warn("Student {} was interrupted", id); + log.warn("Student {} was interrupted", id); Thread.currentThread().interrupt(); } } diff --git a/src/main/java/org/labs/orderedlocks/Waiter.java b/src/main/java/org/labs/orderedlocks/Waiter.java index 53e1ebb..e22cbab 100644 --- a/src/main/java/org/labs/orderedlocks/Waiter.java +++ b/src/main/java/org/labs/orderedlocks/Waiter.java @@ -9,7 +9,7 @@ public class Waiter implements Runnable { - private static final Logger logger = LoggerFactory.getLogger(Waiter.class); + private static final Logger log = LoggerFactory.getLogger(Waiter.class); private final Integer id; private final BlockingQueue> orders; @@ -44,7 +44,7 @@ public void run() { } } } catch (InterruptedException e) { - logger.info("Waiter {} was interrupted", id); + log.info("Waiter {} was interrupted", id); Thread.currentThread().interrupt(); } } From b90d92a5420bedd73fb77561731c4b4ed29b8cab Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Sun, 21 Sep 2025 20:02:11 +0300 Subject: [PATCH 07/12] add lombok, remove static --- build.gradle.kts | 4 ++ src/main/java/org/labs/Main.java | 19 ++++++++ src/main/java/org/labs/common/Config.java | 19 ++++---- src/main/java/org/labs/common/Spoon.java | 11 ++--- src/main/java/org/labs/common/Statistic.java | 8 ++-- ...nts.java => DiningStudentsSimulation.java} | 46 +++++++++++-------- .../java/org/labs/orderedlocks/Kitchen.java | 7 ++- .../java/org/labs/orderedlocks/Student.java | 20 ++++++-- 8 files changed, 91 insertions(+), 43 deletions(-) create mode 100644 src/main/java/org/labs/Main.java rename src/main/java/org/labs/orderedlocks/{DiningStudents.java => DiningStudentsSimulation.java} (68%) diff --git a/build.gradle.kts b/build.gradle.kts index 90c5c85..b7b5acc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id("java") + id("me.champeau.jmh") version "0.7.3" } group = "org.labs" @@ -13,6 +14,9 @@ dependencies { implementation("org.slf4j:slf4j-api:2.0.16") implementation("ch.qos.logback:logback-classic:1.5.13") + compileOnly("org.projectlombok:lombok:1.18.30") + annotationProcessor("org.projectlombok:lombok:1.18.30") + 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 new file mode 100644 index 0000000..77dc51d --- /dev/null +++ b/src/main/java/org/labs/Main.java @@ -0,0 +1,19 @@ +package org.labs; + +import org.labs.common.Config; +import org.labs.orderedlocks.DiningStudentsSimulation; + +public class Main { + public static void main(String[] args) throws InterruptedException { + var config = Config.builder() + .NUMBER_OF_STUDENTS(7) + .NUMBER_OF_SOUP(10_000) + .NUMBER_OF_WAITERS(2) + .TIME_TO_EAT_SOUP_MS(1) + .TIME_TO_SPEAK_MS(2) + .FAIR_IF_POSSIBLE(false) + .build(); + var diningStudents = new DiningStudentsSimulation(config); + diningStudents.simulate(); + } +} diff --git a/src/main/java/org/labs/common/Config.java b/src/main/java/org/labs/common/Config.java index 8397f38..1cf3883 100644 --- a/src/main/java/org/labs/common/Config.java +++ b/src/main/java/org/labs/common/Config.java @@ -1,15 +1,18 @@ package org.labs.common; +import lombok.Builder; +import lombok.RequiredArgsConstructor; + +@Builder +@RequiredArgsConstructor public class Config { - public static final int NUMBER_OF_STUDENTS = 7; - public static final int NUMBER_OF_SOUP = 1_000_000; - public static final int NUMBER_OF_WAITERS = 2; - public static final long TIME_TO_EAT_SOUP_MS = 0; - public static final long TIME_TO_SPEAK_MS = 0; + public final int NUMBER_OF_STUDENTS; + public final int NUMBER_OF_SOUP; + public final int NUMBER_OF_WAITERS; - public static final boolean FAIR_IF_POSSIBLE = true; + public final long TIME_TO_EAT_SOUP_MS; + public final long TIME_TO_SPEAK_MS; - private Config() { - } + public final boolean FAIR_IF_POSSIBLE; } diff --git a/src/main/java/org/labs/common/Spoon.java b/src/main/java/org/labs/common/Spoon.java index 88965aa..ad8a619 100644 --- a/src/main/java/org/labs/common/Spoon.java +++ b/src/main/java/org/labs/common/Spoon.java @@ -2,17 +2,16 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import lombok.Getter; public class Spoon { + @Getter private final Integer id; - private final Lock lock = new ReentrantLock(Config.FAIR_IF_POSSIBLE); + private final Lock lock; - public Spoon(int id) { + public Spoon(int id, boolean fairness) { this.id = id; - } - - public Integer getId() { - return id; + lock = new ReentrantLock(fairness); } public void lock() { diff --git a/src/main/java/org/labs/common/Statistic.java b/src/main/java/org/labs/common/Statistic.java index 75de9f1..ab51170 100644 --- a/src/main/java/org/labs/common/Statistic.java +++ b/src/main/java/org/labs/common/Statistic.java @@ -37,18 +37,18 @@ public void addWaiterStatistic(int waiterId) { public void printStatistic() { StringBuilder sb = new StringBuilder("Statistic:\nStudents: ["); - for (int i = 0; i < Config.NUMBER_OF_STUDENTS; ++i) { + for (int i = 0; i < studentStatistic.size(); ++i) { sb.append(studentStatistic.get(i).get()); - if (i != Config.NUMBER_OF_STUDENTS - 1) { + if (i != studentStatistic.size() - 1) { sb.append(", "); } } sb.append("]\nWaiters: ["); - for (int i = 0; i < Config.NUMBER_OF_WAITERS; ++i) { + for (int i = 0; i < waiterStatistic.size(); ++i) { sb.append(waiterStatistic.get(i).get()); - if (i != Config.NUMBER_OF_WAITERS - 1) { + if (i != waiterStatistic.size() - 1) { sb.append(", "); } } diff --git a/src/main/java/org/labs/orderedlocks/DiningStudents.java b/src/main/java/org/labs/orderedlocks/DiningStudentsSimulation.java similarity index 68% rename from src/main/java/org/labs/orderedlocks/DiningStudents.java rename to src/main/java/org/labs/orderedlocks/DiningStudentsSimulation.java index e4c7631..f986ebe 100644 --- a/src/main/java/org/labs/orderedlocks/DiningStudents.java +++ b/src/main/java/org/labs/orderedlocks/DiningStudentsSimulation.java @@ -13,26 +13,32 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class DiningStudents { +public class DiningStudentsSimulation { - private static final Logger log = LoggerFactory.getLogger(DiningStudents.class); + private static final Logger log = LoggerFactory.getLogger(DiningStudentsSimulation.class); - public static void main(String[] args) throws InterruptedException { - var statistic = new Statistic(Config.NUMBER_OF_STUDENTS, Config.NUMBER_OF_WAITERS); + private final Config config; - List spoons = createSpoons(Config.NUMBER_OF_STUDENTS); + public DiningStudentsSimulation(Config config) { + this.config = config; + } + + public void simulate() throws InterruptedException { + var statistic = new Statistic(config.NUMBER_OF_STUDENTS, config.NUMBER_OF_WAITERS); + + List spoons = createSpoons(config.NUMBER_OF_STUDENTS); - Kitchen kitchen = new Kitchen(); + Kitchen kitchen = new Kitchen(config.NUMBER_OF_SOUP); BlockingQueue> orders = new ArrayBlockingQueue<>( - Config.NUMBER_OF_SOUP + Config.NUMBER_OF_STUDENTS + 1, - Config.FAIR_IF_POSSIBLE + config.NUMBER_OF_SOUP + config.NUMBER_OF_STUDENTS + 1, + config.FAIR_IF_POSSIBLE ); - List waiters = createAndStartWaiters(Config.NUMBER_OF_WAITERS, orders, kitchen, statistic); + List waiters = createAndStartWaiters(config.NUMBER_OF_WAITERS, orders, kitchen, statistic); var startTime = System.nanoTime(); - List students = createAndStartStudents(Config.NUMBER_OF_STUDENTS, spoons, statistic, orders); + List students = createAndStartStudents(config.NUMBER_OF_STUDENTS, spoons, statistic, orders); students.forEach((thread -> { try { @@ -53,15 +59,15 @@ public static void main(String[] args) throws InterruptedException { log.info("Time: {} ms", TimeUnit.NANOSECONDS.toMillis(endTime - startTime)); } - private static List createSpoons(int numberOfSpoons) { + private List createSpoons(int numberOfSpoons) { List spoons = new ArrayList<>(numberOfSpoons); - for (int i = 0; i < Config.NUMBER_OF_STUDENTS; ++i) { - spoons.add(new Spoon(i)); + for (int i = 0; i < config.NUMBER_OF_STUDENTS; ++i) { + spoons.add(new Spoon(i, config.FAIR_IF_POSSIBLE)); } return spoons; } - private static List createAndStartWaiters( + private List createAndStartWaiters( int numberOfWaiters, BlockingQueue> orders, Kitchen kitchen, @@ -77,7 +83,7 @@ private static List createAndStartWaiters( return waiters; } - private static List createAndStartStudents( + private List createAndStartStudents( int numberOfStudents, List spoons, Statistic statistic, @@ -86,15 +92,17 @@ private static List createAndStartStudents( List students = new ArrayList<>(numberOfStudents); for (int i = 0; i < numberOfStudents; ++i) { // all students except for the last should take left spoon first - int firstSpoonId = i != Config.NUMBER_OF_STUDENTS - 1 ? - i == 0 ? Config.NUMBER_OF_STUDENTS - 1 : i - 1 : i; - int secondSpoonId = i != Config.NUMBER_OF_STUDENTS - 1 ? i : i - 1; + int firstSpoonId = i != config.NUMBER_OF_STUDENTS - 1 ? + i == 0 ? config.NUMBER_OF_STUDENTS - 1 : i - 1 : i; + int secondSpoonId = i != config.NUMBER_OF_STUDENTS - 1 ? i : i - 1; var studentRunnable = new Student( i, statistic, spoons.get(firstSpoonId), spoons.get(secondSpoonId), - orders + orders, + config.TIME_TO_SPEAK_MS, + config.TIME_TO_EAT_SOUP_MS ); var studentThread = new Thread(studentRunnable); studentThread.start(); diff --git a/src/main/java/org/labs/orderedlocks/Kitchen.java b/src/main/java/org/labs/orderedlocks/Kitchen.java index 8497cb4..0ce8181 100644 --- a/src/main/java/org/labs/orderedlocks/Kitchen.java +++ b/src/main/java/org/labs/orderedlocks/Kitchen.java @@ -1,20 +1,23 @@ package org.labs.orderedlocks; import java.util.concurrent.atomic.AtomicInteger; -import org.labs.common.Config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Kitchen { private static final Logger log = LoggerFactory.getLogger(Kitchen.class); - private final AtomicInteger soupCount = new AtomicInteger(Config.NUMBER_OF_SOUP); + private final AtomicInteger soupCount; public enum SoupOrderStatus { OK, OUT_OF_SOUP, } + public Kitchen(int initialSoupCount) { + soupCount = new AtomicInteger(initialSoupCount); + } + public SoupOrderStatus getSoup() { Integer currentSoupCount; diff --git a/src/main/java/org/labs/orderedlocks/Student.java b/src/main/java/org/labs/orderedlocks/Student.java index 56100cb..1a3e59e 100644 --- a/src/main/java/org/labs/orderedlocks/Student.java +++ b/src/main/java/org/labs/orderedlocks/Student.java @@ -3,7 +3,6 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import org.labs.common.Config; import org.labs.common.Spoon; import org.labs.common.Statistic; import org.labs.orderedlocks.Kitchen.SoupOrderStatus; @@ -20,12 +19,25 @@ public class Student implements Runnable { private final Spoon secondSpoon; private final BlockingQueue> orders; - public Student(Integer id, Statistic statistic, Spoon firstSpoon, Spoon secondSpoon, BlockingQueue> orders) { + private final long speakTimeMs; + private final long eatTimeMs; + + public Student( + Integer id, + Statistic statistic, + Spoon firstSpoon, + Spoon secondSpoon, + BlockingQueue> orders, + long speakTimeMs, + long eatTimeMs + ) { this.id = id; this.statistic = statistic; this.firstSpoon = firstSpoon; this.secondSpoon = secondSpoon; this.orders = orders; + this.speakTimeMs = speakTimeMs; + this.eatTimeMs = eatTimeMs; } @Override @@ -52,7 +64,7 @@ public void run() { return; } - Thread.sleep(Config.TIME_TO_EAT_SOUP_MS); + Thread.sleep(eatTimeMs); statistic.addStudentStatistic(id); } finally { secondSpoon.unlock(); @@ -75,6 +87,6 @@ public void run() { } private void speak() throws InterruptedException { - Thread.sleep(Config.TIME_TO_SPEAK_MS); + Thread.sleep(speakTimeMs); } } From 3d2be58c9ddd3272434b441f1c418ff7bade5d5c Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Sun, 21 Sep 2025 20:11:30 +0300 Subject: [PATCH 08/12] add jmh test and readme --- README.md | 74 +++++++++++++++++-- .../labs/orderedlocks/OrderedLocksTests.java | 52 +++++++++++++ src/main/java/org/labs/Main.java | 4 +- 3 files changed, 122 insertions(+), 8 deletions(-) create mode 100644 src/jmh/java/org/labs/orderedlocks/OrderedLocksTests.java diff --git a/README.md b/README.md index e974d43..f97f2fa 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ [![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-22041afd0340ce965d47ae6ef1cefeee28c7c493a6346c4f15d667ab976d596c.svg)](https://classroom.github.com/a/qcWcnElX) + # Java concurrency # Цели и задачи л/р: + Задача об обедающих философах: Рассмотрим семь программистов, сидящих вокруг круглого стола для обеда. @@ -9,24 +11,84 @@ Однако, чтобы поесть суп, программисту необходимо взять две ложки - справа и слева (он очень голодный). Когда программист поедает суп, ложки остаются занятыми и не могут быть использованы соседними программистами. Программисты чередуют прием еды с обсуждением преподавателей. -Когда суп заканчивается, программист просит одного из двух официантов принести ему еще одну порцию (то есть тарелка супа ограничена). +Когда суп заканчивается, программист просит одного из двух официантов принести ему еще одну порцию (то есть тарелка супа +ограничена). Всего в ресторане есть 1_000_000 порций еды, после чего обед заканчивается. Все программисты должны поесть +- одинаково, чтобы никому не было обидно - - Ваша задача - реализовать симуляцию обеда с использованием языка программирования Java и многопоточности. -Каждый программист должен быть представлен в виде потока, а ложки - в виде общих ресурсов, которые программисты могут захватывать и освобождать. +Каждый программист должен быть представлен в виде потока, а ложки - в виде общих ресурсов, которые программисты могут +захватывать и освобождать. Также не забудьте про официантов и запасы еды. Дополнительное условие -- количество программистов, еды и официантов должно быть параметризируемое. [Это усложнение классической задачи, про которую можно почитать тут](https://en.wikipedia.org/wiki/Dining_philosophers_problem) -Необходимо обеспечить корректное выполнение программы, чтобы избежать состояний взаимной блокировки и гарантировать, что каждый программист получит возможность поесть. +Необходимо обеспечить корректное выполнение программы, чтобы избежать состояний взаимной блокировки и гарантировать, что +каждый программист получит возможность поесть. # Обязательное условие: + * Использование системы сборки Gradle * Код должен быть отлажен и протестирован -# Дедлайн 08.10.2025 23:59 \ No newline at end of file +# Дедлайн 08.10.2025 23:59 + +# Решения + +## 1. Упорядочивание блокировок + +Решение заключается в том, что последний студент захватывает ложку в другом порядке. +Если все сначала захватывают левую ложку, а затем правую, то последний студент захватывает правую ложку, а затем левую. + +Такое решение **не** обеспечивает fairness (все студенты поедят одинаково). + +Ниже рассмотрены различные варианты вокруг этого подхода. + +### 1.1. Добавление различных задержек + +Решение дает fairness в случае, когда работа **вне** критической секции дольше работы внутри критической секции. + +Это обосновывается тем, что студенты не конкурируют за захват ресурса. + +```java + var config = Config.builder() + .NUMBER_OF_STUDENTS(7) + .NUMBER_OF_SOUP(10_000) + .NUMBER_OF_WAITERS(2) + .TIME_TO_EAT_SOUP_MS(Eat Delay) + .TIME_TO_SPEAK_MS(Speak Delay) + .FAIR_IF_POSSIBLE(false) + .build(); +``` + +| Speak Delay | Eat Delay | Fairness Array | +|-------------|-----------|--------------------------------------------| +| 0 | 0 | [219, 403, 771, 1505, 1778, 5144, 180] | +| 0 | 1 | [574, 858, 1062, 1711, 1862, 3430, 503] | +| 1 | 0 | [1428, 1430, 1428, 1427, 1428, 1430, 1429] | +| 1 | 1 | [1292, 1457, 1481, 1490, 1492, 1496, 1292] | +| 2 | 1 | [1428, 1428, 1429, 1429, 1429, 1429, 1428] | + +### 1.2. Использование параметра `fair` в Java API + +У классов ReentrantLock и ArrayBlockingQueue есть параметр `fair`, который позволяет гарантировать "честность" при +захвате ресурсов, в моем случае это **почему-то** полностью не решает проблему, но значительно улучшает честность. + +При этом стоит помнить, что fairness небесплатная, для демонстрации этого эффекта был реализован JMH +`OrderedLocksTests`, для запуска следует добавить gradlew права на исполнение (`sudo chmod +x ./gradlew`) и вызвать +`./gradlew jmh`. В тесте использовались задержки равные 0. Результат теста будет в `./build/results/jmh/results.txt`. + +Результаты на моей машине (MAC M2 MAX 32GB): + +```text +Benchmark Mode Cnt Score Error Units +OrderedLocksTests.zeroDelayFairTest avgt 5 171.625 ± 13.561 ms/op +OrderedLocksTests.zeroDelayNonFairTest avgt 5 105.592 ± 27.446 ms/op +``` + +| fair | fairness array | +|-------|--------------------------------------------| +| false | [227, 433, 724, 1483, 1690, 5291, 152] | +| true | [1179, 1301, 1249, 1333, 1434, 2322, 1182] | diff --git a/src/jmh/java/org/labs/orderedlocks/OrderedLocksTests.java b/src/jmh/java/org/labs/orderedlocks/OrderedLocksTests.java new file mode 100644 index 0000000..f028cef --- /dev/null +++ b/src/jmh/java/org/labs/orderedlocks/OrderedLocksTests.java @@ -0,0 +1,52 @@ +package org.labs.orderedlocks; + +import java.util.concurrent.TimeUnit; +import org.labs.common.Config; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +@Fork(1) +@State(Scope.Benchmark) +@Warmup(iterations = 2) +@Measurement(iterations = 5) +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +public class OrderedLocksTests { + + private final Config zeroDelayNonFairTest = Config.builder() + .NUMBER_OF_STUDENTS(7) + .NUMBER_OF_SOUP(1_000_0) + .NUMBER_OF_WAITERS(2) + .TIME_TO_EAT_SOUP_MS(0) + .TIME_TO_SPEAK_MS(0) + .FAIR_IF_POSSIBLE(false) + .build(); + + private final Config zeroDelayFairTest = Config.builder() + .NUMBER_OF_STUDENTS(7) + .NUMBER_OF_SOUP(1_000_0) + .NUMBER_OF_WAITERS(2) + .TIME_TO_EAT_SOUP_MS(0) + .TIME_TO_SPEAK_MS(0) + .FAIR_IF_POSSIBLE(true) + .build(); + + @Benchmark + public void zeroDelayNonFairTest() throws InterruptedException { + var diningStudents = new DiningStudentsSimulation(zeroDelayNonFairTest); + diningStudents.simulate(); + } + + @Benchmark + public void zeroDelayFairTest() throws InterruptedException { + var diningStudents = new DiningStudentsSimulation(zeroDelayFairTest); + diningStudents.simulate(); + } +} diff --git a/src/main/java/org/labs/Main.java b/src/main/java/org/labs/Main.java index 77dc51d..14bbed0 100644 --- a/src/main/java/org/labs/Main.java +++ b/src/main/java/org/labs/Main.java @@ -9,8 +9,8 @@ public static void main(String[] args) throws InterruptedException { .NUMBER_OF_STUDENTS(7) .NUMBER_OF_SOUP(10_000) .NUMBER_OF_WAITERS(2) - .TIME_TO_EAT_SOUP_MS(1) - .TIME_TO_SPEAK_MS(2) + .TIME_TO_EAT_SOUP_MS(0) + .TIME_TO_SPEAK_MS(0) .FAIR_IF_POSSIBLE(false) .build(); var diningStudents = new DiningStudentsSimulation(config); From 75b67fdc223898e8282730632abacac2df0bd66a Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Sun, 21 Sep 2025 20:21:21 +0300 Subject: [PATCH 09/12] update logging --- README.md | 2 ++ src/main/java/org/labs/common/Spoon.java | 4 ++++ src/main/java/org/labs/common/Statistic.java | 6 ++---- .../org/labs/orderedlocks/DiningStudentsSimulation.java | 6 ++---- src/main/java/org/labs/orderedlocks/Kitchen.java | 5 ++--- src/main/java/org/labs/orderedlocks/Student.java | 6 ++---- src/main/java/org/labs/orderedlocks/Waiter.java | 6 ++---- 7 files changed, 16 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index f97f2fa..ca1a4da 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,8 @@ Решение заключается в том, что последний студент захватывает ложку в другом порядке. Если все сначала захватывают левую ложку, а затем правую, то последний студент захватывает правую ложку, а затем левую. +Решение расположено в пакете orderedlocks. + Такое решение **не** обеспечивает fairness (все студенты поедят одинаково). Ниже рассмотрены различные варианты вокруг этого подхода. diff --git a/src/main/java/org/labs/common/Spoon.java b/src/main/java/org/labs/common/Spoon.java index ad8a619..88db7ee 100644 --- a/src/main/java/org/labs/common/Spoon.java +++ b/src/main/java/org/labs/common/Spoon.java @@ -21,4 +21,8 @@ public void lock() { public void unlock() { lock.unlock(); } + + public boolean tryLock() { + return lock.tryLock(); + } } diff --git a/src/main/java/org/labs/common/Statistic.java b/src/main/java/org/labs/common/Statistic.java index ab51170..60ce266 100644 --- a/src/main/java/org/labs/common/Statistic.java +++ b/src/main/java/org/labs/common/Statistic.java @@ -3,13 +3,11 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; +@Slf4j public class Statistic { - private static final Logger log = LoggerFactory.getLogger(Statistic.class); - private final List studentStatistic; private final List waiterStatistic; diff --git a/src/main/java/org/labs/orderedlocks/DiningStudentsSimulation.java b/src/main/java/org/labs/orderedlocks/DiningStudentsSimulation.java index f986ebe..dbe3969 100644 --- a/src/main/java/org/labs/orderedlocks/DiningStudentsSimulation.java +++ b/src/main/java/org/labs/orderedlocks/DiningStudentsSimulation.java @@ -6,17 +6,15 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; import org.labs.common.Config; import org.labs.common.Spoon; import org.labs.common.Statistic; import org.labs.orderedlocks.Kitchen.SoupOrderStatus; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +@Slf4j public class DiningStudentsSimulation { - private static final Logger log = LoggerFactory.getLogger(DiningStudentsSimulation.class); - private final Config config; public DiningStudentsSimulation(Config config) { diff --git a/src/main/java/org/labs/orderedlocks/Kitchen.java b/src/main/java/org/labs/orderedlocks/Kitchen.java index 0ce8181..bf0ed45 100644 --- a/src/main/java/org/labs/orderedlocks/Kitchen.java +++ b/src/main/java/org/labs/orderedlocks/Kitchen.java @@ -1,12 +1,11 @@ package org.labs.orderedlocks; import java.util.concurrent.atomic.AtomicInteger; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; +@Slf4j public class Kitchen { - private static final Logger log = LoggerFactory.getLogger(Kitchen.class); private final AtomicInteger soupCount; public enum SoupOrderStatus { diff --git a/src/main/java/org/labs/orderedlocks/Student.java b/src/main/java/org/labs/orderedlocks/Student.java index 1a3e59e..15e4ac3 100644 --- a/src/main/java/org/labs/orderedlocks/Student.java +++ b/src/main/java/org/labs/orderedlocks/Student.java @@ -3,16 +3,14 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import lombok.extern.slf4j.Slf4j; import org.labs.common.Spoon; import org.labs.common.Statistic; import org.labs.orderedlocks.Kitchen.SoupOrderStatus; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +@Slf4j public class Student implements Runnable { - private static final Logger log = LoggerFactory.getLogger(Student.class); - private final Integer id; private final Statistic statistic; private final Spoon firstSpoon; diff --git a/src/main/java/org/labs/orderedlocks/Waiter.java b/src/main/java/org/labs/orderedlocks/Waiter.java index e22cbab..849bd32 100644 --- a/src/main/java/org/labs/orderedlocks/Waiter.java +++ b/src/main/java/org/labs/orderedlocks/Waiter.java @@ -2,15 +2,13 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.CompletableFuture; +import lombok.extern.slf4j.Slf4j; import org.labs.common.Statistic; import org.labs.orderedlocks.Kitchen.SoupOrderStatus; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +@Slf4j public class Waiter implements Runnable { - private static final Logger log = LoggerFactory.getLogger(Waiter.class); - private final Integer id; private final BlockingQueue> orders; private final Kitchen kitchen; From 0d3fc294a0f94781afef050050a0b306c18c2825 Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Sun, 21 Sep 2025 21:30:40 +0300 Subject: [PATCH 10/12] move kitchen to common --- .../{orderedlocks => common}/Kitchen.java | 2 +- .../DiningStudentsSimulation.java | 3 ++- .../java/org/labs/orderedlocks/Student.java | 22 +++---------------- .../java/org/labs/orderedlocks/Waiter.java | 3 ++- 4 files changed, 8 insertions(+), 22 deletions(-) rename src/main/java/org/labs/{orderedlocks => common}/Kitchen.java (96%) diff --git a/src/main/java/org/labs/orderedlocks/Kitchen.java b/src/main/java/org/labs/common/Kitchen.java similarity index 96% rename from src/main/java/org/labs/orderedlocks/Kitchen.java rename to src/main/java/org/labs/common/Kitchen.java index bf0ed45..c95d6b1 100644 --- a/src/main/java/org/labs/orderedlocks/Kitchen.java +++ b/src/main/java/org/labs/common/Kitchen.java @@ -1,4 +1,4 @@ -package org.labs.orderedlocks; +package org.labs.common; import java.util.concurrent.atomic.AtomicInteger; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/org/labs/orderedlocks/DiningStudentsSimulation.java b/src/main/java/org/labs/orderedlocks/DiningStudentsSimulation.java index dbe3969..290aa4b 100644 --- a/src/main/java/org/labs/orderedlocks/DiningStudentsSimulation.java +++ b/src/main/java/org/labs/orderedlocks/DiningStudentsSimulation.java @@ -8,9 +8,10 @@ import java.util.concurrent.TimeUnit; import lombok.extern.slf4j.Slf4j; import org.labs.common.Config; +import org.labs.common.Kitchen; +import org.labs.common.Kitchen.SoupOrderStatus; import org.labs.common.Spoon; import org.labs.common.Statistic; -import org.labs.orderedlocks.Kitchen.SoupOrderStatus; @Slf4j public class DiningStudentsSimulation { diff --git a/src/main/java/org/labs/orderedlocks/Student.java b/src/main/java/org/labs/orderedlocks/Student.java index 15e4ac3..6b6434d 100644 --- a/src/main/java/org/labs/orderedlocks/Student.java +++ b/src/main/java/org/labs/orderedlocks/Student.java @@ -3,12 +3,14 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.labs.common.Kitchen.SoupOrderStatus; import org.labs.common.Spoon; import org.labs.common.Statistic; -import org.labs.orderedlocks.Kitchen.SoupOrderStatus; @Slf4j +@RequiredArgsConstructor public class Student implements Runnable { private final Integer id; @@ -20,24 +22,6 @@ public class Student implements Runnable { private final long speakTimeMs; private final long eatTimeMs; - public Student( - Integer id, - Statistic statistic, - Spoon firstSpoon, - Spoon secondSpoon, - BlockingQueue> orders, - long speakTimeMs, - long eatTimeMs - ) { - this.id = id; - this.statistic = statistic; - this.firstSpoon = firstSpoon; - this.secondSpoon = secondSpoon; - this.orders = orders; - this.speakTimeMs = speakTimeMs; - this.eatTimeMs = eatTimeMs; - } - @Override public void run() { try { diff --git a/src/main/java/org/labs/orderedlocks/Waiter.java b/src/main/java/org/labs/orderedlocks/Waiter.java index 849bd32..77aa873 100644 --- a/src/main/java/org/labs/orderedlocks/Waiter.java +++ b/src/main/java/org/labs/orderedlocks/Waiter.java @@ -3,8 +3,9 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.CompletableFuture; import lombok.extern.slf4j.Slf4j; +import org.labs.common.Kitchen; import org.labs.common.Statistic; -import org.labs.orderedlocks.Kitchen.SoupOrderStatus; +import org.labs.common.Kitchen.SoupOrderStatus; @Slf4j public class Waiter implements Runnable { From e02f39afd45af7e3328c8575e3c893ac772ddc10 Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Sun, 21 Sep 2025 23:10:53 +0300 Subject: [PATCH 11/12] add semaphore solution --- README.md | 29 ++++ .../labs/orderedlocks/OrderedLocksTests.java | 4 +- .../labs/semaphore/SemaphoreLocksTests.java | 52 +++++++ src/main/java/org/labs/Main.java | 19 --- .../DiningStudentsSimulation.java | 13 ++ .../semaphore/DiningStudentsSimulation.java | 127 ++++++++++++++++++ .../java/org/labs/semaphore/SpoonArbiter.java | 20 +++ src/main/java/org/labs/semaphore/Student.java | 80 +++++++++++ src/main/java/org/labs/semaphore/Waiter.java | 45 +++++++ 9 files changed, 368 insertions(+), 21 deletions(-) create mode 100644 src/jmh/java/org/labs/semaphore/SemaphoreLocksTests.java delete mode 100644 src/main/java/org/labs/Main.java create mode 100644 src/main/java/org/labs/semaphore/DiningStudentsSimulation.java create mode 100644 src/main/java/org/labs/semaphore/SpoonArbiter.java create mode 100644 src/main/java/org/labs/semaphore/Student.java create mode 100644 src/main/java/org/labs/semaphore/Waiter.java diff --git a/README.md b/README.md index ca1a4da..1e8049d 100644 --- a/README.md +++ b/README.md @@ -94,3 +94,32 @@ OrderedLocksTests.zeroDelayNonFairTest avgt 5 105.592 ± 27.446 ms/op |-------|--------------------------------------------| | false | [227, 433, 724, 1483, 1690, 5291, 152] | | true | [1179, 1301, 1249, 1333, 1434, 2322, 1182] | + +## 2. Использование арбитра + +Решение заключается в использовании специального класса-арбитра, который следит за числом одновременно едящих студентов, +это число не должно превышать число студентов - 1. Этот инвариант реализуется через семафор. + +Решение находится в пакете semaphore. + +Аналогично `ReentrantLock` `Semaphore` имеет параметр `fair`, который вынесен в config. + +Это решение показывает более высокую честность, чем решение с упорядочиванием блокировок, даже при `fair = false`. + +| fair | fairness array | +|-------|--------------------------------------------| +| false | [1430, 1424, 1446, 1437, 1454, 1436, 1373] | +| true | [1428, 1429, 1428, 1429, 1429, 1430, 1427] | + +При этом fairness для семафора тоже не бесплатная, для демонстрации этого эффекта был реализован JMH тест +`SemaphoreLocksTests`, запуск аналогичен решению с упорядочиванием блокировок. + +Полные результаты: + +```text +Benchmark Mode Cnt Score Error Units +o.l.orderedlocks.OrderedLocksTests.zeroDelayFairTest avgt 5 170.558 ± 26.265 ms/op +o.l.orderedlocks.OrderedLocksTests.zeroDelayNonFairTest avgt 5 106.313 ± 9.687 ms/op +o.l.semaphore.SemaphoreLocksTests.zeroDelayFairTest avgt 5 157.910 ± 62.556 ms/op +o.l.semaphore.SemaphoreLocksTests.zeroDelayNonFairTest avgt 5 117.113 ± 25.536 ms/op +``` diff --git a/src/jmh/java/org/labs/orderedlocks/OrderedLocksTests.java b/src/jmh/java/org/labs/orderedlocks/OrderedLocksTests.java index f028cef..1ddd792 100644 --- a/src/jmh/java/org/labs/orderedlocks/OrderedLocksTests.java +++ b/src/jmh/java/org/labs/orderedlocks/OrderedLocksTests.java @@ -22,7 +22,7 @@ public class OrderedLocksTests { private final Config zeroDelayNonFairTest = Config.builder() .NUMBER_OF_STUDENTS(7) - .NUMBER_OF_SOUP(1_000_0) + .NUMBER_OF_SOUP(10_000) .NUMBER_OF_WAITERS(2) .TIME_TO_EAT_SOUP_MS(0) .TIME_TO_SPEAK_MS(0) @@ -31,7 +31,7 @@ public class OrderedLocksTests { private final Config zeroDelayFairTest = Config.builder() .NUMBER_OF_STUDENTS(7) - .NUMBER_OF_SOUP(1_000_0) + .NUMBER_OF_SOUP(10_000) .NUMBER_OF_WAITERS(2) .TIME_TO_EAT_SOUP_MS(0) .TIME_TO_SPEAK_MS(0) diff --git a/src/jmh/java/org/labs/semaphore/SemaphoreLocksTests.java b/src/jmh/java/org/labs/semaphore/SemaphoreLocksTests.java new file mode 100644 index 0000000..a7e73e2 --- /dev/null +++ b/src/jmh/java/org/labs/semaphore/SemaphoreLocksTests.java @@ -0,0 +1,52 @@ +package org.labs.semaphore; + +import java.util.concurrent.TimeUnit; +import org.labs.common.Config; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +@Fork(1) +@State(Scope.Benchmark) +@Warmup(iterations = 2) +@Measurement(iterations = 5) +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +public class SemaphoreLocksTests { + + private final Config zeroDelayNonFairTest = Config.builder() + .NUMBER_OF_STUDENTS(7) + .NUMBER_OF_SOUP(10_000) + .NUMBER_OF_WAITERS(2) + .TIME_TO_EAT_SOUP_MS(0) + .TIME_TO_SPEAK_MS(0) + .FAIR_IF_POSSIBLE(false) + .build(); + + private final Config zeroDelayFairTest = Config.builder() + .NUMBER_OF_STUDENTS(7) + .NUMBER_OF_SOUP(10_000) + .NUMBER_OF_WAITERS(2) + .TIME_TO_EAT_SOUP_MS(0) + .TIME_TO_SPEAK_MS(0) + .FAIR_IF_POSSIBLE(true) + .build(); + + @Benchmark + public void zeroDelayNonFairTest() throws InterruptedException { + var diningStudents = new DiningStudentsSimulation(zeroDelayNonFairTest); + diningStudents.simulate(); + } + + @Benchmark + public void zeroDelayFairTest() throws InterruptedException { + var diningStudents = new DiningStudentsSimulation(zeroDelayFairTest); + diningStudents.simulate(); + } +} diff --git a/src/main/java/org/labs/Main.java b/src/main/java/org/labs/Main.java deleted file mode 100644 index 14bbed0..0000000 --- a/src/main/java/org/labs/Main.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.labs; - -import org.labs.common.Config; -import org.labs.orderedlocks.DiningStudentsSimulation; - -public class Main { - public static void main(String[] args) throws InterruptedException { - var config = Config.builder() - .NUMBER_OF_STUDENTS(7) - .NUMBER_OF_SOUP(10_000) - .NUMBER_OF_WAITERS(2) - .TIME_TO_EAT_SOUP_MS(0) - .TIME_TO_SPEAK_MS(0) - .FAIR_IF_POSSIBLE(false) - .build(); - var diningStudents = new DiningStudentsSimulation(config); - diningStudents.simulate(); - } -} diff --git a/src/main/java/org/labs/orderedlocks/DiningStudentsSimulation.java b/src/main/java/org/labs/orderedlocks/DiningStudentsSimulation.java index 290aa4b..a4e14c2 100644 --- a/src/main/java/org/labs/orderedlocks/DiningStudentsSimulation.java +++ b/src/main/java/org/labs/orderedlocks/DiningStudentsSimulation.java @@ -16,6 +16,19 @@ @Slf4j public class DiningStudentsSimulation { + public static void main(String[] args) throws InterruptedException { + var config = Config.builder() + .NUMBER_OF_STUDENTS(7) + .NUMBER_OF_SOUP(1_000_000) + .NUMBER_OF_WAITERS(2) + .TIME_TO_EAT_SOUP_MS(0) + .TIME_TO_SPEAK_MS(0) + .FAIR_IF_POSSIBLE(false) + .build(); + var diningStudents = new DiningStudentsSimulation(config); + diningStudents.simulate(); + } + private final Config config; public DiningStudentsSimulation(Config config) { diff --git a/src/main/java/org/labs/semaphore/DiningStudentsSimulation.java b/src/main/java/org/labs/semaphore/DiningStudentsSimulation.java new file mode 100644 index 0000000..283ba29 --- /dev/null +++ b/src/main/java/org/labs/semaphore/DiningStudentsSimulation.java @@ -0,0 +1,127 @@ +package org.labs.semaphore; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; +import org.labs.common.Config; +import org.labs.common.Kitchen; +import org.labs.common.Kitchen.SoupOrderStatus; +import org.labs.common.Spoon; +import org.labs.common.Statistic; + +@Slf4j +public class DiningStudentsSimulation { + + public static void main(String[] args) throws InterruptedException { + var config = Config.builder() + .NUMBER_OF_STUDENTS(7) + .NUMBER_OF_SOUP(1_000_0) + .NUMBER_OF_WAITERS(2) + .TIME_TO_EAT_SOUP_MS(0) + .TIME_TO_SPEAK_MS(0) + .FAIR_IF_POSSIBLE(true) + .build(); + var diningStudents = new DiningStudentsSimulation(config); + diningStudents.simulate(); + } + + private final Config config; + + public DiningStudentsSimulation(Config config) { + this.config = config; + } + + public void simulate() throws InterruptedException { + var statistic = new Statistic(config.NUMBER_OF_STUDENTS, config.NUMBER_OF_WAITERS); + + List spoons = createSpoons(config.NUMBER_OF_STUDENTS); + var arbiter = new SpoonArbiter(config.NUMBER_OF_STUDENTS, config.FAIR_IF_POSSIBLE); + + Kitchen kitchen = new Kitchen(config.NUMBER_OF_SOUP); + + BlockingQueue> orders = new ArrayBlockingQueue<>( + config.NUMBER_OF_SOUP + config.NUMBER_OF_STUDENTS + 1, + config.FAIR_IF_POSSIBLE + ); + + List waiters = createAndStartWaiters(config.NUMBER_OF_WAITERS, orders, kitchen, statistic); + + var startTime = System.nanoTime(); + List students = createAndStartStudents(config.NUMBER_OF_STUDENTS, spoons, statistic, arbiter, orders); + + students.forEach((thread -> { + try { + thread.join(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }) + ); + var endTime = System.nanoTime(); + + for (var waiter : waiters) { + waiter.interrupt(); + waiter.join(); + } + + statistic.printStatistic(); + log.info("Time: {} ms", TimeUnit.NANOSECONDS.toMillis(endTime - startTime)); + } + + private List createSpoons(int numberOfSpoons) { + List spoons = new ArrayList<>(numberOfSpoons); + for (int i = 0; i < config.NUMBER_OF_STUDENTS; ++i) { + spoons.add(new Spoon(i, config.FAIR_IF_POSSIBLE)); + } + return spoons; + } + + private List createAndStartWaiters( + int numberOfWaiters, + BlockingQueue> orders, + Kitchen kitchen, + Statistic statistic + ) { + List waiters = new ArrayList<>(numberOfWaiters); + for (int i = 0; i < numberOfWaiters; ++i) { + var waiterRunnable = new Waiter(i, orders, kitchen, statistic); + var waiterThread = new Thread(waiterRunnable); + waiterThread.start(); + waiters.add(waiterThread); + } + return waiters; + } + + private List createAndStartStudents( + int numberOfStudents, + List spoons, + Statistic statistic, + SpoonArbiter arbiter, + BlockingQueue> orders + ) { + List students = new ArrayList<>(numberOfStudents); + for (int i = 0; i < numberOfStudents; ++i) { + // all students except for the last should take left spoon first + int firstSpoonId = i == 0 ? numberOfStudents - 1 : i - 1; + int secondSpoonId = i; + var studentRunnable = new Student( + i, + statistic, + spoons.get(firstSpoonId), + spoons.get(secondSpoonId), + arbiter, + orders, + config.TIME_TO_SPEAK_MS, + config.TIME_TO_EAT_SOUP_MS + ); + var studentThread = new Thread(studentRunnable); + studentThread.start(); + students.add(studentThread); + } + return students; + } +} diff --git a/src/main/java/org/labs/semaphore/SpoonArbiter.java b/src/main/java/org/labs/semaphore/SpoonArbiter.java new file mode 100644 index 0000000..fe12edc --- /dev/null +++ b/src/main/java/org/labs/semaphore/SpoonArbiter.java @@ -0,0 +1,20 @@ +package org.labs.semaphore; + +import java.util.concurrent.Semaphore; + +public class SpoonArbiter { + + private final Semaphore semaphore; + + public SpoonArbiter(int numberOfStudents, boolean fairness) { + this.semaphore = new Semaphore(numberOfStudents - 1, fairness); + } + + public void acquire() throws InterruptedException { + semaphore.acquire(); + } + + public void release() { + semaphore.release(); + } +} diff --git a/src/main/java/org/labs/semaphore/Student.java b/src/main/java/org/labs/semaphore/Student.java new file mode 100644 index 0000000..2149330 --- /dev/null +++ b/src/main/java/org/labs/semaphore/Student.java @@ -0,0 +1,80 @@ +package org.labs.semaphore; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.labs.common.Kitchen.SoupOrderStatus; +import org.labs.common.Spoon; +import org.labs.common.Statistic; + +@Slf4j +@RequiredArgsConstructor +public class Student implements Runnable { + + private final Integer id; + private final Statistic statistic; + private final Spoon firstSpoon; + private final Spoon secondSpoon; + private final SpoonArbiter arbiter; + private final BlockingQueue> orders; + + private final long speakTimeMs; + private final long eatTimeMs; + + @Override + public void run() { + try { + while (true) { + try { + speak(); + + arbiter.acquire(); + try { + firstSpoon.lock(); + try { + log.debug("Student {} took spoon with id: {}", id, firstSpoon.getId()); + secondSpoon.lock(); + try { + log.debug("Student {} took spoon with id: {}", id, secondSpoon.getId()); + + CompletableFuture order = new CompletableFuture<>(); + orders.put(order); + + var orderStatus = order.get(); + + if (orderStatus == SoupOrderStatus.OUT_OF_SOUP) { + log.info("no more food for {}, leaving restaurant", id); + return; + } + + Thread.sleep(eatTimeMs); + statistic.addStudentStatistic(id); + } finally { + secondSpoon.unlock(); + log.debug("Student {} put down spoon with id: {}", id, secondSpoon.getId()); + } + } finally { + firstSpoon.unlock(); + log.debug("Student {} put down spoon with id: {}", id, firstSpoon.getId()); + } + } finally { + arbiter.release(); + } + } catch (ExecutionException e) { + // should not happen + log.error("Student {} received error, stopping", id, e); + return; + } + } + } catch (InterruptedException e) { + log.warn("Student {} was interrupted", id); + Thread.currentThread().interrupt(); + } + } + + private void speak() throws InterruptedException { + Thread.sleep(speakTimeMs); + } +} diff --git a/src/main/java/org/labs/semaphore/Waiter.java b/src/main/java/org/labs/semaphore/Waiter.java new file mode 100644 index 0000000..a0ebe54 --- /dev/null +++ b/src/main/java/org/labs/semaphore/Waiter.java @@ -0,0 +1,45 @@ +package org.labs.semaphore; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletableFuture; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.labs.common.Kitchen; +import org.labs.common.Kitchen.SoupOrderStatus; +import org.labs.common.Statistic; + +@Slf4j +@RequiredArgsConstructor +public class Waiter implements Runnable { + + private final Integer id; + private final BlockingQueue> orders; + private final Kitchen kitchen; + private final Statistic statistic; + + @Override + public void run() { + try { + while (true) { + CompletableFuture order = null; + try { + order = orders.take(); + SoupOrderStatus orderStatus = kitchen.getSoup(); + + order.complete(orderStatus); + + if (orderStatus != SoupOrderStatus.OUT_OF_SOUP) { + statistic.addWaiterStatistic(id); + } + } finally { + if (order != null) { + order.complete(SoupOrderStatus.OUT_OF_SOUP); + } + } + } + } catch (InterruptedException e) { + log.info("Waiter {} was interrupted", id); + Thread.currentThread().interrupt(); + } + } +} From e65e6b76323f9e875b8caf6cac0d00a577996eff Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Sun, 21 Sep 2025 23:19:18 +0300 Subject: [PATCH 12/12] delete outdated comment --- src/main/java/org/labs/semaphore/DiningStudentsSimulation.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/labs/semaphore/DiningStudentsSimulation.java b/src/main/java/org/labs/semaphore/DiningStudentsSimulation.java index 283ba29..7bdf5ff 100644 --- a/src/main/java/org/labs/semaphore/DiningStudentsSimulation.java +++ b/src/main/java/org/labs/semaphore/DiningStudentsSimulation.java @@ -105,7 +105,6 @@ private List createAndStartStudents( ) { List students = new ArrayList<>(numberOfStudents); for (int i = 0; i < numberOfStudents; ++i) { - // all students except for the last should take left spoon first int firstSpoonId = i == 0 ? numberOfStudents - 1 : i - 1; int secondSpoonId = i; var studentRunnable = new Student(