Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ build/
!**/src/test/**/build/

### IntelliJ IDEA ###
.idea/
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
Expand Down
31 changes: 26 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,51 @@
[![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-22041afd0340ce965d47ae6ef1cefeee28c7c493a6346c4f15d667ab976d596c.svg)](https://classroom.github.com/a/qcWcnElX)

# Java concurrency

# Результаты

В последних столбца приведены перцентили распределения кол-ва съеденных программистами порций

| Программистов | Официантов | Порций | Время (ms) | p50 | p90 | p95 | p100 |
|---------------|------------|---------|------------|--------|--------|--------|--------|
| 2 | 1 | 300 | 10084 | 150.00 | 150.00 | 150.00 | 150.00 |
| 20 | 1 | 300 | 1514 | 15.00 | 16.30 | 19.00 | 19.00 |
| 200 | 1 | 300 | 505 | 1.50 | 2.00 | 2.00 | 2.00 |
| 10000 | 1 | 600000 | 8030 | 60.00 | 63.00 | 64.00 | 124.00 |
| 10000 | 10 | 600000 | 8531 | 60.00 | 64.00 | 65.00 | 128.00 |
| 10000 | 1000 | 600000 | 8536 | 60.00 | 64.00 | 65.00 | 128.00 |
| 10000 | 1000 | 1000000 | 16532 | 99.00 | 105.00 | 106.00 | 251.00 |
| 20000 | 1000 | 1000000 | 6551 | 50.00 | 53.00 | 54.00 | 68.00 |
| 200000 | 1000 | 1000000 | 8123 | 5.00 | 6.00 | 6.00 | 7.00 |

# Цели и задачи л/р:

Задача об обедающих философах:

Рассмотрим семь программистов, сидящих вокруг круглого стола для обеда.
У каждого программиста есть тарелка супа перед ним, а между каждой парой программистов находится ложка.
Однако, чтобы поесть суп, программисту необходимо взять две ложки - справа и слева (он очень голодный).
Когда программист поедает суп, ложки остаются занятыми и не могут быть использованы соседними программистами.
Программисты чередуют прием еды с обсуждением преподавателей.
Когда суп заканчивается, программист просит одного из двух официантов принести ему еще одну порцию (то есть тарелка супа ограничена).
Когда суп заканчивается, программист просит одного из двух официантов принести ему еще одну порцию (то есть тарелка супа
ограничена).
Всего в ресторане есть 1_000_000 порций еды, после чего обед заканчивается.
Все программисты должны поесть +- одинаково, чтобы никому не было обидно



Ваша задача - реализовать симуляцию обеда с использованием языка программирования Java и многопоточности.
Каждый программист должен быть представлен в виде потока, а ложки - в виде общих ресурсов, которые программисты могут захватывать и освобождать.
Каждый программист должен быть представлен в виде потока, а ложки - в виде общих ресурсов, которые программисты могут
захватывать и освобождать.
Также не забудьте про официантов и запасы еды.

Дополнительное условие -- количество программистов, еды и официантов должно быть параметризируемое.

[Это усложнение классической задачи, про которую можно почитать тут](https://en.wikipedia.org/wiki/Dining_philosophers_problem)

Необходимо обеспечить корректное выполнение программы, чтобы избежать состояний взаимной блокировки и гарантировать, что каждый программист получит возможность поесть.
Необходимо обеспечить корректное выполнение программы, чтобы избежать состояний взаимной блокировки и гарантировать, что
каждый программист получит возможность поесть.

# Обязательное условие:

* Использование системы сборки Gradle
* Код должен быть отлажен и протестирован

Expand Down
12 changes: 12 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,20 @@ repositories {
}

dependencies {
implementation("org.slf4j:slf4j-api:2.0.16")
implementation("ch.qos.logback:logback-classic:1.5.13")

compileOnly("org.projectlombok:lombok:1.18.42")
annotationProcessor("org.projectlombok:lombok:1.18.42")

testCompileOnly("org.projectlombok:lombok:1.18.42")
testAnnotationProcessor("org.projectlombok:lombok:1.18.42")

testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
testImplementation(platform("org.junit:junit-bom:5.10.0"))
testImplementation("org.junit.jupiter:junit-jupiter")

testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.0")
}

tasks.test {
Expand Down
83 changes: 81 additions & 2 deletions src/main/java/org/labs/Main.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,86 @@
package org.labs;

import java.util.Map;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.labs.hierarchy.DinnerFactory;
import org.labs.hierarchy.DinnerResult;

@Slf4j
public class Main {

public static final int[] PERCENTILES = {50, 90, 95, 100};

@SneakyThrows
public static void main(String[] args) {
System.out.println("Hello, World!");
int programmersCount;
int waitersCount;
int servings;
programmersCount = 2;
waitersCount = 1;
servings = 300;

dinnerSample(programmersCount, waitersCount, servings);

programmersCount = 20;
dinnerSample(programmersCount, waitersCount, servings);

programmersCount = 200;
dinnerSample(programmersCount, waitersCount, servings);
//
programmersCount = 10_000;
waitersCount = 1;
servings = 600_000;

dinnerSample(programmersCount, waitersCount, servings);

waitersCount = 10;
dinnerSample(programmersCount, waitersCount, servings);

waitersCount = 1000;
dinnerSample(programmersCount, waitersCount, servings);

programmersCount = 10_000;
waitersCount = 1000;
servings = 1_000_000;

dinnerSample(programmersCount, waitersCount, servings);

programmersCount = 20_000;
dinnerSample(programmersCount, waitersCount, servings);

programmersCount = 200_000;
dinnerSample(programmersCount, waitersCount, servings);
}

private static void dinnerSample(int programmersCount, int waitersCount, int servings) {
DinnerFactory dinnerFactory = new DinnerFactory(
programmersCount,
waitersCount,
servings,
Executors.newVirtualThreadPerTaskExecutor(),
Executors.newVirtualThreadPerTaskExecutor()
);

DinnerResult dinnerResult = dinnerFactory.setupAndRun();
logResults(dinnerResult);
}


private static void logResults(DinnerResult dinnerResult) {
log.info("Each programmer had servings by percentiles: \n{}", printPercentiles(Utils.calculatePercentiles(
dinnerResult.servingsEaten(),
PERCENTILES
)));
}

private static String printPercentiles(Map<Integer, Double> percentilesTable) {
return percentilesTable.entrySet().stream()
.map(e -> String.format(" p%d: %10.2f", e.getKey(), e.getValue()))
.collect(Collectors.joining("\n"));

}
}
}
63 changes: 63 additions & 0 deletions src/main/java/org/labs/Utils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package org.labs;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

public class Utils {
private static final Random RANDOM = new Random();

public static int discussTime() {
return random(10, 20);
}

public static int eatTime() {
return random(20, 40);
}

public static int random(int min, int max) {
return RANDOM.nextInt(min, max);
}

public static Map<Integer, Double> calculatePercentiles(List<Integer> data, int... percentiles) {
List<Integer> sortedData = new ArrayList<>(data);
Collections.sort(sortedData);

Map<Integer, Double> results = new LinkedHashMap<>();
for (int p : percentiles) {
results.put(p, calculatePercentile(sortedData, p));
}
return results;
}

private static double calculatePercentile(List<Integer> sortedList, int percentile) {
if (sortedList == null || sortedList.isEmpty()) {
throw new IllegalArgumentException("List cannot be null or empty");
}
if (percentile < 0 || percentile > 100) {
throw new IllegalArgumentException("Percentile must be between 0 and 100");
}

int n = sortedList.size();

if (percentile == 0) return sortedList.getFirst();
if (percentile == 100) return sortedList.get(n - 1);

double rank = percentile / 100.0 * (n - 1);
int lowerIndex = (int) Math.floor(rank);
int upperIndex = (int) Math.ceil(rank);

if (lowerIndex == upperIndex) {
return sortedList.get(lowerIndex);
}

double lowerValue = sortedList.get(lowerIndex);
double upperValue = sortedList.get(upperIndex);
double fraction = rank - lowerIndex;

return lowerValue + fraction * (upperValue - lowerValue);
}
}
99 changes: 99 additions & 0 deletions src/main/java/org/labs/hierarchy/DinnerFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package org.labs.hierarchy;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RequiredArgsConstructor
public class DinnerFactory {

private final int programmersCount;
private final int waitersCount;
private final int servingsCount;
private final ExecutorService programmersPool;
private final ExecutorService waitersPool;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Чисто как симуляция норм, но фактически -- может можно обойтись и без очереди официантов? Да/нет и почему?


@SneakyThrows
public DinnerResult setupAndRun() {
log.info(
"Starting dinner with \n{} programmers \n{} waiters \n{} servings",
programmersCount,
waitersCount,
servingsCount
);

List<Spoon> spoons = createSpoons();
Restaurant restaurant = new Restaurant(servingsCount);
List<Programmer> programmers = createProgrammers(restaurant, spoons);
List<Waiter> waiters = createWaiters(restaurant);

long startTime = (long) (System.nanoTime() / 1e6);

programmers.forEach(programmersPool::submit);
waiters.forEach(waitersPool::submit);

// blocking current thread
monitorRestaurant(restaurant);

long finishTime = (long) (System.nanoTime() / 1e6);

programmersPool.shutdown();
waitersPool.shutdown();
try {
if (!programmersPool.awaitTermination(2, TimeUnit.SECONDS)) {
programmersPool.shutdownNow();
}
} catch (InterruptedException e) {
programmersPool.shutdownNow();
}

try {
if (!waitersPool.awaitTermination(2, TimeUnit.SECONDS)) {
waitersPool.shutdownNow();
}
} catch (InterruptedException e) {
waitersPool.shutdownNow();
}

log.info("All servings were eaten in {} ms", finishTime - startTime);
return new DinnerResult(
programmers.stream()
.map(Programmer::getTotalServings)
.collect(Collectors.toList()),
restaurant.getFoodServings()
);
}

@SneakyThrows
private void monitorRestaurant(Restaurant restaurant) {
while (restaurant.isFoodAvailable()) {
Thread.sleep(500);
// log.info("Restaurant have {} servings left", restaurant.getFoodServings());
}
}

private List<Spoon> createSpoons() {
return IntStream.range(0, programmersCount)
.mapToObj(Spoon::new)
.toList();
}

private List<Programmer> createProgrammers(Restaurant restaurant, List<Spoon> spoons) {
return IntStream.range(0, programmersCount)
.mapToObj(i -> new Programmer(i, spoons.get(i), spoons.get((i + 1) % programmersCount), restaurant))
.toList();
}

private List<Waiter> createWaiters(Restaurant restaurant) {
return IntStream.range(0, waitersCount)
.mapToObj(i -> new Waiter(restaurant))
.toList();
}
}
9 changes: 9 additions & 0 deletions src/main/java/org/labs/hierarchy/DinnerResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.labs.hierarchy;

import java.util.List;

public record DinnerResult(
List<Integer> servingsEaten,
int servingsLeft
) {
}
36 changes: 36 additions & 0 deletions src/main/java/org/labs/hierarchy/FoodRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.labs.hierarchy;

import java.util.concurrent.CountDownLatch;

import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;

@RequiredArgsConstructor
public final class FoodRequest implements Comparable<FoodRequest> {
private final int clientId;
private final int alreadyEaten;
private final CountDownLatch latch = new CountDownLatch(1);
private boolean isServed = false;

@SneakyThrows
public boolean getServed() {
latch.await();
return isServed;
}

public void setServed() {
isServed = true;
latch.countDown();
}

public void setUnserved() {
isServed = false;
latch.countDown();
}


@Override
public int compareTo(FoodRequest o) {
return Integer.compare(alreadyEaten, o.alreadyEaten);
}
}
Loading