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
13 changes: 0 additions & 13 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,17 +1,4 @@
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/

### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
Expand Down
9 changes: 9 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM openjdk:24-jdk-slim

WORKDIR /app

COPY . .

RUN chmod +x gradlew

RUN ./gradlew build --no-daemon
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
run_homework:
docker compose up --build

test_homework:
docker compose --profile homework1_test run --rm --build homework1_test
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
29 changes: 29 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
plugins {
id 'java'
id 'application'
}

repositories {
mavenCentral()
}

dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.10.0'
}

application {
mainClass = 'org.example.app.Main'
}

configurations {
jcstress
}

test {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
showStandardStreams = true
}
}
12 changes: 12 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
services:
homework1:
build: .
container_name: homework_1_run
command: ./gradlew run --no-daemon

homework1_test:
build: .
container_name: homework1_test
profiles:
- homework1_test
command: ./gradlew clean test
32 changes: 32 additions & 0 deletions readme.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-22041afd0340ce965d47ae6ef1cefeee28c7c493a6346c4f15d667ab976d596c.svg)](https://classroom.github.com/a/qcWcnElX)
# Java concurrency

# Цели и задачи л/р:
Задача об обедающих философах:

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



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

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

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

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

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

# Дедлайн 08.10.2025 23:59
3 changes: 3 additions & 0 deletions src/main/config.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
iterations=10
programmers_count=50
food_count=1000
30 changes: 30 additions & 0 deletions src/main/java/org/example/app/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.example.app;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;

import org.example.dinner.*;

public class Main {
public static void main(String[] args) throws InterruptedException, IOException {
Properties props = new Properties();
props.load(new FileInputStream("src/main/config.properties"));

int iterations = Integer.parseInt(props.getProperty("iterations", "10"));
int fixedProgrammers = Integer.parseInt(props.getProperty("programmers_count", "-1"));
int fixedFood = Integer.parseInt(props.getProperty("food_count", "-1"));

for (int i = 0; i < iterations; i++) {
AtomicInteger foodCount = new AtomicInteger(fixedFood);

System.out.println("--------------------------------------------------------------------");
System.out.println("Programmers: " + fixedProgrammers);
System.out.println("Food: " + fixedFood);

Dinner dinner = new Dinner();
dinner.serve(fixedProgrammers, foodCount);
}
}
}
48 changes: 48 additions & 0 deletions src/main/java/org/example/dinner/Dinner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.example.dinner;

import org.example.programmer.Programmer;
import org.example.fork.Fork;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class Dinner {

// Dependencies
private final DinnerVerifier verifier = new DinnerVerifier();
private final ResourceGenerator resourceGenerator = new ResourceGenerator();
private final ArrayList<Programmer> programmers = new ArrayList<>();
private final BlockingQueue<Fork> forks = new LinkedBlockingQueue<>();
private HashMap<Integer, Integer> counts = new HashMap<>();

public void serve(int programmersCount, AtomicInteger foodCount) throws InterruptedException {
// Verifying the values to not run with garbage ones
verifier.execute(programmersCount, foodCount);
// Generating resources
resourceGenerator.generate(programmers, forks, programmersCount, foodCount);
// Starting dinner
ExecutorService pool = Executors.newFixedThreadPool(programmersCount);
for (Programmer p : programmers) {
pool.submit(p);
}
pool.shutdown();
pool.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
System.out.println("Dinner is over!");
System.out.println("Food amount is " + foodCount);
// Counting stats to display
for (Programmer programmer : programmers) {
int count = counts.getOrDefault(programmer.getPortionsEaten(), 0);
counts.put(programmer.getPortionsEaten(), count + 1);
}
// Displaying stats
for (Map.Entry<Integer, Integer> entry : counts.entrySet()) {
int portionsEaten = entry.getKey();
int count = entry.getValue();
System.out.println("Portions eaten: " + portionsEaten + ", Count: " + count);
}
System.out.println("--------------------------------------------------------------------");
}
}
15 changes: 15 additions & 0 deletions src/main/java/org/example/dinner/DinnerVerifier.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.example.dinner;

import java.util.concurrent.atomic.AtomicInteger;

public class DinnerVerifier {
public void execute(int ProgrammersCount, AtomicInteger FoodCount) throws IllegalArgumentException {
// Verifying that the amount can't be negative
if (ProgrammersCount <= 0 || FoodCount.get() <= 0) {
throw new IllegalArgumentException("Amount can't be negative");
}
if (FoodCount.get() < ProgrammersCount) {
throw new IllegalArgumentException("Some of programmers would still be hungry");
}
}
}
29 changes: 29 additions & 0 deletions src/main/java/org/example/dinner/ResourceGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.example.dinner;

import org.example.programmer.Programmer;
import org.example.fork.Fork;

import java.util.ArrayList;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;

public class ResourceGenerator {
public void generate(
ArrayList<Programmer> programmers,
BlockingQueue<Fork> forks,
int amount,
AtomicInteger foodAmount
) {
Semaphore availableForks = new Semaphore(amount, true);
// Generating the N amount of forks
for (int i = 0; i < amount; i++) {
forks.add(new Fork(i));
}
// The same amount of programmers
for (int i = 0; i < amount; i++) {
programmers.add(new Programmer(i, forks, foodAmount, availableForks));
}
}
}
19 changes: 19 additions & 0 deletions src/main/java/org/example/fork/Fork.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.example.fork;

import java.util.concurrent.locks.ReentrantLock;

// Just fork
public class Fork implements Comparable<Fork> {
int id;
private final ReentrantLock lock = new ReentrantLock(true);

public Fork(int id) {
this.id = id;
}
public int getId() { return id; }

@Override
public int compareTo(Fork other) {
return Integer.compare(this.id, other.id);
}
}
102 changes: 102 additions & 0 deletions src/main/java/org/example/programmer/Programmer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package org.example.programmer;

import org.example.fork.Fork;
import java.lang.*;
import java.util.HashMap;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;

public class Programmer implements Runnable {

// Resources of the thread
private final int id;
final HashMap<Integer, Fork> userForks;
private int portionsEaten;
private final Semaphore availableForks;

// Mutual resources shared between threads
private final BlockingQueue<Fork> forks;
private final AtomicInteger foodLeft;

public Programmer(
int id,
BlockingQueue<Fork> forks,
AtomicInteger foodLeft,
Semaphore availableForks
) {
// Resources of the thread
this.id = id;
this.userForks = new HashMap<>();
this.portionsEaten = 0;
this.availableForks = availableForks;

// Mutual resources shared between threads
this.forks = forks;
this.foodLeft = foodLeft;
}

@Override
public void run() {
try {
// Trying to eat while food is here
while (takeOnePortionIfAvailable()) {
boolean ate = false;
// Trying while the programmer eat
while (!ate) {
// If grabbed forks - eat
if (grabForks()) {
try {
EatDinnder();
ate = true;
} finally {
releaseForks();
}
}
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}

private boolean takeOnePortionIfAvailable() {
int cur = foodLeft.get();
// No food available - stop
if (cur <= 0) return false;
if (foodLeft.compareAndSet(cur, cur - 1)) return true;
return false;
}

public int getProgId(){ return this.id; }
public int getPortionsEaten(){ return this.portionsEaten; }

private boolean grabForks() {
try {
availableForks.acquire(2);
Fork first = forks.take();
Fork second = forks.take();
userForks.put(1, first);
userForks.put(2, second);
return true;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}

private void releaseForks() throws InterruptedException {
Fork first = userForks.remove(1);
Fork second = userForks.remove(2);
if (first != null) forks.put(first);
if (second != null) forks.put(second);
availableForks.release(2);
}

private void EatDinnder() throws InterruptedException {
// Increment the eaten portions
this.portionsEaten++;
// We are eating
Thread.sleep(10);
}
}
Loading