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
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 @@ -12,6 +12,11 @@ repositories {
dependencies {
testImplementation(platform("org.junit:junit-bom:5.10.0"))
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.openjdk.jmh:jmh-core:1.36")

implementation("commons-cli:commons-cli:1.5.0")
implementation("org.slf4j:slf4j-api:2.0.10")
implementation("ch.qos.logback:logback-classic:1.5.13")
}

tasks.test {
Expand Down
65 changes: 64 additions & 1 deletion src/main/java/org/labs/Main.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,70 @@
package org.labs;

import org.apache.commons.cli.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.labs.lunch.Restaurant;

import java.util.concurrent.TimeUnit;

public class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
final Logger logger = LoggerFactory.getLogger(Main.class);

int programmersCount = 7;
int waitersCount = 2;
int portionsCount = 5_000;
int timeout = 60;

Options options = new Options();
options.addOption("p", "programmers", true, "Number of programmers");
options.addOption("w", "waiters", true, "Number of waiters");
options.addOption("f", "food", true, "Total portions count");
options.addOption("t", "timeout", true, "Timeout in seconds");

CommandLineParser parser = new DefaultParser();
try {
CommandLine cmd = parser.parse(options, args);

if (cmd.hasOption("p")) {
programmersCount = Integer.parseInt(cmd.getOptionValue("p"));
}
if (cmd.hasOption("w")) {
waitersCount = Integer.parseInt(cmd.getOptionValue("w"));
}
if (cmd.hasOption("f")) {
portionsCount = Integer.parseInt(cmd.getOptionValue("f"));
}
if (cmd.hasOption("t")) {
timeout = Integer.parseInt(cmd.getOptionValue("t"));
}
} catch (ParseException e) {
logger.error("Error parsing arguments", e);
new HelpFormatter().printHelp("dining-philosophers", options);
return;
}

logger.info(
"Starting simulation with {} programmers, {} waiters, {} portions",
programmersCount,
waitersCount,
portionsCount
);

Restaurant restaurant = new Restaurant(programmersCount, waitersCount, portionsCount);
restaurant.start();

try {
if (!restaurant.awaitCompletion(timeout, TimeUnit.SECONDS)) {
logger.error("Simulation timed out!");
restaurant.shutdownNow();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
restaurant.shutdownNow();
} finally {
restaurant.printStatistics();
}
}
}
52 changes: 52 additions & 0 deletions src/main/java/org/labs/lunch/DinningTable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package org.labs.lunch;

public class DinningTable {

private final int programmersCount;
private final Spoon[] spoons;

public DinningTable(int programmersCount) {
this.programmersCount = programmersCount;

this.spoons = new Spoon[programmersCount];
for (int i = 0; i < programmersCount; i++) {
this.spoons[i] = new Spoon(i);
}

}

private class SpoonPair {
int firstSpoonId;
int secondSpoonId;

SpoonPair(int firstSpoonId, int secondSpoonId) {
this.firstSpoonId = firstSpoonId;
this.secondSpoonId = secondSpoonId;
}
}

private SpoonPair getOrderedSpoonIds(int programmerId) {
int leftSpoonId = programmerId;
int rightSpoonId = (programmerId + 1) % programmersCount;

if (leftSpoonId < rightSpoonId) {
return new SpoonPair(leftSpoonId, rightSpoonId);
} else {
return new SpoonPair(rightSpoonId, leftSpoonId);
}
}

void takeSpoons(int programmerId) {
SpoonPair spoonPair = getOrderedSpoonIds(programmerId);

spoons[spoonPair.firstSpoonId].lock();
spoons[spoonPair.secondSpoonId].lock();
}

void putSpoons(int programmerId) {
SpoonPair spoonPair = getOrderedSpoonIds(programmerId);

spoons[spoonPair.secondSpoonId].unlock();
spoons[spoonPair.firstSpoonId].unlock();
}
}
84 changes: 84 additions & 0 deletions src/main/java/org/labs/lunch/Programmer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package org.labs.lunch;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.ThreadLocalRandom;

public class Programmer implements Runnable {
private static final Logger logger = LoggerFactory.getLogger(Programmer.class);

private final int id;
private int portionsEaten = 0;
private volatile boolean hasSoupPortion;
private final Restaurant restaurant;
private final ThreadLocalRandom random = ThreadLocalRandom.current();
private final Object plateMonitor = new Object();

private final long minThinkTime = 20;
private final long maxThinkTime = 40;
private final long minEatTime = 10;
private final long maxEatTime = 20;

public Programmer(int programmerId, Restaurant restaurant) {
this.id = programmerId;
this.hasSoupPortion = false;
this.restaurant = restaurant;
}

@Override
public void run() {
try {
while (restaurant.isRunning() && (restaurant.getPortionsCount().get() > 0 || hasSoupPortion)) {
think();

synchronized (plateMonitor) {
while (!hasSoupPortion && restaurant.isRunning()) {
if (restaurant.getPortionsCount().get() == 0) {
return;
}

restaurant.requestPortion(this);
plateMonitor.wait();
}
}

restaurant.getDinningTable().takeSpoons(id);
eat();
restaurant.getDinningTable().putSpoons(id);
}
} catch (InterruptedException ex) {
logger.debug("Programmer {} interrupted", id);
Thread.currentThread().interrupt();
} catch (Exception ex) {
logger.error("Programmer {} encountered unexpected exception", id, ex);
}
}

private void think() throws InterruptedException {
long duration = random.nextLong(minThinkTime, maxThinkTime);
logger.debug("Programmer {} thinking for {}ms", id, duration);
Thread.sleep(duration);
}

private void eat() throws InterruptedException {
long duration = random.nextLong(minEatTime, maxEatTime);
logger.debug("Programmer {} eating for {}ms", id, duration);
Thread.sleep(duration);

hasSoupPortion = false;
portionsEaten += 1;
}

public void refillPlateWithSoup() {
synchronized (plateMonitor) {
hasSoupPortion = true;
plateMonitor.notifyAll();
}
}


public int getId() { return id; }
public int getPortionsEaten() { return portionsEaten; }
public Object getPlateMonitor() { return plateMonitor; }
}
106 changes: 106 additions & 0 deletions src/main/java/org/labs/lunch/Restaurant.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package org.labs.lunch;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class Restaurant {
private static final Logger logger = LoggerFactory.getLogger(Restaurant.class);

private final AtomicInteger portionsCount;
private final PriorityBlockingQueue<Programmer> portionsAskQueue;

private final List<Programmer> programmersList;
private final ExecutorService programmersExecutor;

private final int waitersCount;
private final ExecutorService waitersExecutor;

private final DinningTable dinningTable;

private volatile boolean isRunning = true;

public Restaurant(int programmersCount, int waiterCount, int portionsCount) {
this.portionsCount = new AtomicInteger(portionsCount);
this.portionsAskQueue = new PriorityBlockingQueue<>(
programmersCount,
Comparator.comparingInt(Programmer::getPortionsEaten)
);
this.programmersList = new ArrayList<>();
this.programmersExecutor = Executors.newFixedThreadPool(programmersCount);
this.waitersCount = waiterCount;
this.waitersExecutor = Executors.newFixedThreadPool(waiterCount);
this.dinningTable = new DinningTable(programmersCount);

for (int i = 0; i < programmersCount; i++) {
Programmer programmer = new Programmer(i, this);
programmersList.add(programmer);
}
}

public void start() {
for (Programmer programmer : programmersList) {
programmersExecutor.submit(programmer);
}

for (int i = 0; i < this.waitersCount; i++) {
Waiter waiter = new Waiter(i, this);
waitersExecutor.submit(waiter);
}
}

public boolean awaitCompletion(long timeout, TimeUnit unit) throws InterruptedException {
programmersExecutor.shutdown();
boolean completed = programmersExecutor.awaitTermination(timeout, unit);
isRunning = false;
waitersExecutor.shutdownNow();
return completed;
}

public void shutdownNow() {
isRunning = false;
programmersExecutor.shutdownNow();
waitersExecutor.shutdownNow();
}

public void printStatistics() {
int totalEaten = 0;
for (Programmer programmer : programmersList) {
int eaten = programmer.getPortionsEaten();
logger.info("Programmer {} ate {} portions", programmer.getId(), eaten);
totalEaten += eaten;
}

logger.info("Total portions eaten: {} (remaining: {})", totalEaten, portionsCount.get());

double average = totalEaten / (double) programmersList.size();
double fairnessThreshold = average * 0.01;

for (Programmer programmer : programmersList) {
int eaten = programmer.getPortionsEaten();
double deviation = Math.abs(eaten - average);
if (deviation > fairnessThreshold) {
logger.warn("Programmer {} deviation too high: {} > {}", programmer.getId(), deviation, fairnessThreshold);
}
}
}

void requestPortion(Programmer programmer) throws InterruptedException {
portionsAskQueue.put(programmer);
}


public AtomicInteger getPortionsCount() { return portionsCount; }
public PriorityBlockingQueue<Programmer> getPortionsAskQueue() { return portionsAskQueue; }
public List<Programmer> getProgrammersList() { return programmersList; }
public DinningTable getDinningTable() { return dinningTable; }
public boolean isRunning() { return isRunning; }
}
21 changes: 21 additions & 0 deletions src/main/java/org/labs/lunch/Spoon.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.labs.lunch;

import java.util.concurrent.locks.ReentrantLock;

public class Spoon {
private final int id;
private final ReentrantLock lock;

public Spoon(int id) {
this.id = id;
lock = new ReentrantLock(true);
}

public void lock() {
lock.lock();
}

public void unlock() {
lock.unlock();
}
}
Loading