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
16 changes: 16 additions & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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
5 changes: 5 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@ repositories {
}

dependencies {
implementation("org.openjdk.jcstress:jcstress-core:0.16")
testImplementation(platform("org.junit:junit-bom:5.10.0"))
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation ("org.junit.jupiter:junit-jupiter-params:5.9.3")
}

tasks.test {
useJUnitPlatform()
testLogging {
showStandardStreams = false
}
}
25 changes: 24 additions & 1 deletion src/main/java/org/labs/Main.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,30 @@
package org.labs;

import org.labs.config.AppConfig;
import org.labs.config.ExecutorType;
import org.labs.model.Restaurant;

public class Main {

public static void main(String[] args) {
Copy link

Choose a reason for hiding this comment

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

Понимаю, что вы потестили через мейн. Но чисто по условиям таски -- сделайте пж несколько тестов. Можно в качестве ассертов проверять равномерность накормленности. А так достаточно тестов на успешное выполнение для нескольких конфигураций

System.out.println("Hello, World!");
int numProgrammers = AppConfig.getNumProgrammers();
int numWaiters = AppConfig.getNumWaiters();
int maxMeals = AppConfig.getMaxMeals();
ExecutorType executorType = AppConfig.getExecutorType();
Restaurant restaurant = new Restaurant(maxMeals, numProgrammers, numWaiters, executorType);

long startTime = System.currentTimeMillis();
restaurant.startDinner();


restaurant.awaitCompletion();

long endTime = System.currentTimeMillis();
long duration = endTime - startTime;

restaurant.printStatistics();

System.out.println("\nTotal dinner time: " + duration + " ms");
System.out.println("Dinner completed successfully!");
}
}
52 changes: 52 additions & 0 deletions src/main/java/org/labs/config/AppConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package org.labs.config;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class AppConfig {
private static final Properties props = new Properties();

static {
try (InputStream input = AppConfig.class.getClassLoader()
.getResourceAsStream("config.properties")) {
if (input == null) {
System.err.println("Config file not found! Using defaults");
setDefaults();
} else {
props.load(input);
}
} catch (IOException e) {
System.err.println("Error loading config: " + e.getMessage());
setDefaults();
}
}

private static void setDefaults() {
props.setProperty("num.programmers", "7");
props.setProperty("num.waiters", "3");
props.setProperty("max.meals", "1_000_000");
props.setProperty("executor.type", "VIRTUAL_THREADS");
}

public static int getNumProgrammers() {
return Integer.parseInt(props.getProperty("num.programmers"));
}

public static int getNumWaiters() {
return Integer.parseInt(props.getProperty("num.waiters"));
}

public static int getMaxMeals() {
return Integer.parseInt(props.getProperty("max.meals"));
}

public static ExecutorType getExecutorType() {
try {
return ExecutorType.valueOf(props.getProperty("executor.type").toUpperCase());
} catch (IllegalArgumentException e) {
System.err.println("Invalid executor type in config, using VIRTUAL_THREADS");
return ExecutorType.VIRTUAL_THREADS;
}
}
}
8 changes: 8 additions & 0 deletions src/main/java/org/labs/config/ExecutorType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.labs.config;

public enum ExecutorType {
VIRTUAL_THREADS,
FIXED_THREAD_POOL,
CACHED_THREAD_POOL,
WORK_STEALING_POOL,
}
28 changes: 28 additions & 0 deletions src/main/java/org/labs/model/Kitchen.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.labs.model;

import java.util.concurrent.atomic.AtomicInteger;

public class Kitchen {
private final AtomicInteger remainingMeals;
private volatile boolean isOpen = true;

public Kitchen(int totalMeals) {
this.remainingMeals = new AtomicInteger(totalMeals);
}

public boolean tryTakeMeal() {
return remainingMeals.getAndUpdate(current -> current > 0 ? current - 1 : current) > 0;
}

public boolean hasMeals() {
return remainingMeals.get() > 0 && isOpen;
}

public int getRemainingMeals() {
return remainingMeals.get();
}

public void close() {
isOpen = false;
}
}
110 changes: 110 additions & 0 deletions src/main/java/org/labs/model/Programmer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package org.labs.model;

import java.util.Comparator;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

class Programmer implements Runnable {
private final int id;
private final Spoon firstSpoon;
private final Spoon secondSpoon;
private final Waiter waiter;
private final AtomicInteger mealsEaten = new AtomicInteger(0);
private final AtomicBoolean permissionToEat = new AtomicBoolean(false);
private volatile boolean running = true;

public Programmer(int id, Spoon leftSpoon, Spoon rightSpoon, Waiter waiter) {
this.id = id;
this.waiter = waiter;

// Определяем порядок взятия ложек для избежания deadlock
if (leftSpoon.getId() < rightSpoon.getId()) {
this.firstSpoon = leftSpoon;
this.secondSpoon = rightSpoon;
} else {
this.firstSpoon = rightSpoon;
this.secondSpoon = leftSpoon;
}
}

@Override
public void run() {
try {
while (running && waiter.isKitchenOpen()) {
think();
requestToEat();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
System.out.println("Programmer " + id + " finished. Eaten: " + mealsEaten.get() + " meals");
}
}

private void think() throws InterruptedException {
System.out.println("Programmer " + id + " is thinking");
Copy link

Choose a reason for hiding this comment

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

Как будто они у вас думают мгновенно. См. коммент про время приема пищи

Thread.sleep(ThreadLocalRandom.current().nextInt(100, 200));
}

private void requestToEat() throws InterruptedException {
if (!running || !waiter.isKitchenOpen()) {
return;
}

permissionToEat.set(false);
waiter.placeOrder(this);

synchronized (this) {
if (!permissionToEat.get()) {
wait(5000); // Долгий таймаут, просто чтобы не блокировать навсегда
}
}

if (permissionToEat.get() && running) {
eat();
}
}

private void eat() throws InterruptedException {
System.out.println("Programmer " + id + " has food, waiting for spoons...");

firstSpoon.acquire();
try {
secondSpoon.acquire();

Thread.sleep(ThreadLocalRandom.current().nextInt(100, 200));
try {
System.out.println("Programmer " + id + " is eating (" + (mealsEaten.get() + 1) + " meal)");
mealsEaten.incrementAndGet();
Copy link

Choose a reason for hiding this comment

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

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

System.out.println("Programmer " + id + " finished eating");
} finally {
secondSpoon.release();
}
} finally {
firstSpoon.release();
}
}

public void receiveMeal() {
permissionToEat.set(true);
synchronized (this) {
notify();
}
}

public void stop() {
running = false;
synchronized (this) {
notifyAll();
}
}

public static Comparator<Programmer> getMealsEatenComparator() {
return Comparator.comparingInt(Programmer::getMealsEaten);
}

public int getMealsEaten() { return mealsEaten.get(); }
public void setPermissionToEat(boolean permission) { permissionToEat.set(permission); }
public int getId() { return id; }
}
Loading