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 README.md
Original file line number Diff line number Diff line change
@@ -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

# Цели и задачи л/р:
Expand Down
9 changes: 9 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
plugins {
id("java")
id("io.freefair.lombok") version "8.13.1"
}

group = "org.labs"
version = "1.0-SNAPSHOT"

java {
sourceCompatibility = JavaVersion.VERSION_21
}

repositories {
mavenCentral()
}

dependencies {
implementation("org.slf4j:slf4j-api:2.0.17")
implementation("ch.qos.logback:logback-classic:1.5.18")
implementation("org.projectlombok:lombok:1.18.42")

testImplementation(platform("org.junit:junit-bom:5.10.0"))
testImplementation("org.junit.jupiter:junit-jupiter")
}
Expand Down
Empty file modified gradlew
100644 → 100755
Empty file.
40 changes: 39 additions & 1 deletion src/main/java/org/labs/Main.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,45 @@
package org.labs;

import lombok.extern.slf4j.Slf4j;
import org.labs.io.ParamsReader;
import org.labs.service.FoodService;
import org.labs.service.QueueService;

import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import static org.labs.configuration.ConfigurationParam.EAT_COUNT;
import static org.labs.configuration.ConfigurationParam.PROGRAMMERS_COUNT;
import static org.labs.configuration.ConfigurationParam.WAITERS_COUNT;

@Slf4j
public class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
var reader = new ParamsReader();

var params = reader.getParamsAsMap("src/main/resources/params");
var eatCount = params.get(EAT_COUNT);

var queueService = new QueueService();
var foodService = new FoodService(eatCount);

var waiters = Executors.newFixedThreadPool(params.get(WAITERS_COUNT));
var programmers = Executors.newFixedThreadPool(params.get(PROGRAMMERS_COUNT));
Copy link

@kechinvv kechinvv Sep 22, 2025

Choose a reason for hiding this comment

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

Хорошо, что взяли экзекуторы, но сможете объяснить, почему именно Fixed? Может есть варианты получше? Если есть, то почему (и если нет, то тот же вопрос)?

Copy link
Author

Choose a reason for hiding this comment

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

Число потоков (программистов) заранее известно из конфига и жить эти потоки должны на протяжении всей программы, что и делает метод newFixedThreadPool

Выписка из доки:

Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue.

Другие реализации будто бы не подходят для данной задачи. Например, cachedThreadPool более применим к короткоживущим асинхронным задачам, это не подходит под описание задачи. ScheduledThreadPool тоже не подходит, так как у нас тут нет какого-то расписания выполнения.


var simulation = new Simulation(params, queueService, foodService);
simulation.run(waiters, programmers);

waiters.shutdown();
programmers.shutdown();
try {
waiters.awaitTermination(60, TimeUnit.SECONDS);
programmers.awaitTermination(60, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}

log.info("Симуляция завершена. Сколько поел каждый:");
foodService.getProgrammerIdToSoupCount().forEach((id, count) ->
log.info("Программист {} поел {} раз", id, count));
}
}
67 changes: 67 additions & 0 deletions src/main/java/org/labs/Simulation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package org.labs;

import lombok.extern.slf4j.Slf4j;
import org.labs.configuration.ConfigurationParam;
import org.labs.model.Fork;
import org.labs.model.Programmer;
import org.labs.model.Waiter;
import org.labs.service.FoodService;
import org.labs.service.QueueService;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.locks.ReentrantLock;

import static org.labs.configuration.ConfigurationParam.PROGRAMMERS_COUNT;
import static org.labs.configuration.ConfigurationParam.TIMEOUT_MS;
import static org.labs.configuration.ConfigurationParam.WAITERS_COUNT;

@Slf4j
public class Simulation {
private final Map<ConfigurationParam, Integer> params;
private final QueueService queueService;
private final FoodService foodService;

private final List<Fork> forks;

public Simulation(Map<ConfigurationParam, Integer> params, QueueService queueService, FoodService foodService) {
this.params = params;
this.queueService = queueService;
this.foodService = foodService;
this.forks = new ArrayList<>();
}

public void run(ExecutorService waiters, ExecutorService programmers) {
log.info("Начали симуляцию");
foodService.initSoups(params.get(PROGRAMMERS_COUNT));
fillForks(params.get(PROGRAMMERS_COUNT));
fillProgrammers(params.get(PROGRAMMERS_COUNT), programmers, params.get(TIMEOUT_MS));
createWaiters(params.get(WAITERS_COUNT), waiters, params.get(TIMEOUT_MS));
}

private void fillForks(int forksCount) {
for (int i = 0; i < forksCount; i++) {
forks.add(new Fork(i + 1, new ReentrantLock()));
}
}

private void fillProgrammers(int programmersCount, ExecutorService executor, long timeoutMs) {
for (int i = 0; i < programmersCount; i++) {
var programmer = new Programmer(i + 1, foodService, queueService, timeoutMs);
programmer.setState(Programmer.State.HUNGRY);
var left = forks.get(i);
var right = forks.get((i + 1) % programmersCount);
programmer.setLeft(left.id() < right.id() ? left : right);
programmer.setRight(left.id() < right.id() ? right : left);
executor.submit(programmer);
}
}

private void createWaiters(int waitersCount, ExecutorService executor, long timeoutMs) {
for (int i = 0; i < waitersCount; i++) {
executor.submit(new Waiter(i + 1, queueService, foodService, timeoutMs));
}
}
}
27 changes: 27 additions & 0 deletions src/main/java/org/labs/configuration/ConfigurationParam.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.labs.configuration;

import lombok.Getter;

@Getter
public enum ConfigurationParam {
PROGRAMMERS_COUNT("programmers_count"),
EAT_COUNT("eat_count"),
WAITERS_COUNT("waiters_count"),
TIMEOUT_MS("timeout_ms"),
;

private final String value;

ConfigurationParam(String value) {
this.value = value;
}

public static ConfigurationParam fromValue(String value) {
for (ConfigurationParam param : values()) {
if (param.value.equals(value)) {
return param;
}
}
throw new IllegalArgumentException("No enum constant with value " + value);
}
}
26 changes: 26 additions & 0 deletions src/main/java/org/labs/io/ParamsReader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.labs.io;

import org.labs.configuration.ConfigurationParam;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map;
import java.util.stream.Collectors;

public class ParamsReader {
private static final String SEPARATOR = "=";

public Map<ConfigurationParam, Integer> getParamsAsMap(String filePath) {
try (var lines = Files.lines(Paths.get(filePath))) {
return lines
.map(line -> line.split(SEPARATOR))
.collect(Collectors.toMap(
values -> ConfigurationParam.fromValue(values[0]),
values -> Integer.parseInt(values[1])
));
} catch (IOException e) {
throw new RuntimeException("Can't read file " + filePath, e);
}
}
}
21 changes: 21 additions & 0 deletions src/main/java/org/labs/model/Fork.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.labs.model;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.Lock;

@Slf4j
public record Fork(
int id,
Lock lock
) {
public void pickUp(int programmerId) {
lock.lock();
log.info("Программист {} взял вилку с id {}", programmerId, id);
}

public void pickDown(int programmerId) {
lock.unlock();
log.info("Программист {} положил вилку с id {}", programmerId, id);
}
}
86 changes: 86 additions & 0 deletions src/main/java/org/labs/model/Programmer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package org.labs.model;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.labs.service.FoodService;
import org.labs.service.QueueService;

@Getter
@RequiredArgsConstructor
@Slf4j
public class Programmer implements Runnable {
private final int id;
@Setter
private State state;
@Setter
private Fork left;
@Setter
private Fork right;

private final FoodService foodService;
private final QueueService queueService;
private final long timeoutMs;

@Override
@SneakyThrows
public void run() {
while (foodService.getEatCount().get() > 0) {
this.state = State.HUNGRY;
log.info("Программист: {}, осталось еды - {}", id, foodService.getEatCount());

while (!foodService.hasSoup(id)) {
if (foodService.getEatCount().get() < 1) {
break;
}
log.info("Нет супа, программист с id = {} не может поесть. Он будет ждать, когда пополнится порция", id);
if (queueService.contains(id)) {
log.warn("Программист {} уже в очереди", id);
}
if (!queueService.contains(id) && foodService.getEatCount().get() > 0) {
queueService.put(id);
}

Thread.sleep(timeoutMs);
// сказать, что нет супа и дождаться, пока он появиться, то есть кинуть поток в сон
}

if (foodService.hasSoup(id) && foodService.getEatCount().get() > 0) {
try {
left.pickUp(id);
right.pickUp(id);

state = State.EATING;

log.info("Программист {} начал кушать суп", id);

foodService.disableSoup(id);

} finally {
log.info("Программист {} закончил есть суп и собирается положить вилки", id);
right.pickDown(id);
left.pickDown(id);

log.info("Программист {} положил вилки", id);
}

log.info("Программист {} начал разговаривать", id);

state = State.TALKING;
Thread.sleep(timeoutMs);
}
}
// он должен думать
// посмотреть, есть ли у него суп
// если нет - попросить официанта налить и продолжить думать
// если есть - взять ложку раз, взять ложку два, начать есть суп (если все съел - отпустить ложки и начать думать)
}

public enum State {
TALKING,
EATING,
HUNGRY;
}
}
34 changes: 34 additions & 0 deletions src/main/java/org/labs/model/Waiter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.labs.model;

import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.labs.service.FoodService;
import org.labs.service.QueueService;

@RequiredArgsConstructor
@Slf4j
public class Waiter implements Runnable {
private final int id;
private final QueueService queueService;
private final FoodService foodService;
private final long timeoutMs;

@Override
@SneakyThrows
public void run() {
while (foodService.getEatCount().get() > 0) {
log.info("Официант {}, его очередь = {}", id, queueService.print());
var programmerId = queueService.poll();
log.info("Официант {} взял программиста с id = {}", id, programmerId);
if (programmerId != null) {
if (foodService.addSoupToProgrammer(programmerId)) {
log.info("Официант {} добавил программисту {} суп", id, programmerId);
} else {
break;
}
}
Thread.sleep(timeoutMs);
}
}
}
Loading