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
37 changes: 37 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: CI

on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4

- name: Make gradlew executable
run: chmod +x gradlew

- name: Run tests
run: ./gradlew test --no-daemon

- name: Upload test report
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: build/reports/tests/test
5 changes: 5 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
plugins {
id("java")
application
}

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

application {
mainClass.set("org.labs.Main")
}

repositories {
mavenCentral()
}
Expand Down
Empty file modified gradlew
100644 → 100755
Empty file.
20 changes: 20 additions & 0 deletions src/main/java/org/labs/ConfigLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.labs;

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

public class ConfigLoader {
public static Properties loadProps() throws IOException {
Properties props = new Properties();
try (InputStream input = ConfigLoader.class
.getClassLoader()
.getResourceAsStream("application.properties")) {
if (input == null) {
throw new IOException("application.properties не найден в classpath");
}
props.load(input);
}
return props;
}
}
31 changes: 30 additions & 1 deletion src/main/java/org/labs/Main.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,36 @@
package org.labs;

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

public class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
int programmers = 0;
int waiters = 0;
int dishes = 0;

try {
Properties props = ConfigLoader.loadProps();
programmers = Integer.parseInt(props.getProperty("amount_around_table"));
waiters = Integer.parseInt(props.getProperty("waiters"));
dishes = Integer.parseInt(props.getProperty("dishes"));
} catch (IOException | NumberFormatException e) {
System.err.println("Не удалось загрузить конфигурацию: " + e.getMessage());
System.exit(1);
}

if (programmers < 2 || waiters < 1 || dishes < 1) {
System.err.println("Некорректная конфигурация: programmers=" + programmers
+ ", waiters=" + waiters + ", dishes=" + dishes);
System.exit(1);
}

Table table = new Table(programmers, waiters, dishes);
table.run();

for(var p : table.getInfo())
System.out.println(p.getEaten());

System.out.println("total: " + table.getInfo().stream().mapToInt(i -> i.getEaten().get()).sum() + " dishes eaten");
}
}
75 changes: 75 additions & 0 deletions src/main/java/org/labs/Programmer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package org.labs;

import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

public class Programmer implements Runnable {

private final Semaphore semaphore = new Semaphore(0);
private final Semaphore doneSemaphore = new Semaphore(0);
private volatile boolean runnable = true;
private final Runnable r;
private final AtomicInteger eaten;
private final AtomicBoolean hasWaiter;

public Programmer(Runnable r) {
this(r, new AtomicInteger(0), new AtomicBoolean(false));
}

public Programmer(Runnable r, AtomicInteger eaten, AtomicBoolean hasWaiter) {
this.r = r;
this.eaten = eaten;
this.hasWaiter = hasWaiter;
}

public AtomicInteger getEaten() {
return eaten;
}

public void setEaten(int value) {
eaten.set(value);
}

public AtomicBoolean getHasWaiter() {
return hasWaiter;
}

public void setHasWaiter(boolean value) {
hasWaiter.set(value);
}

@Override
public void run() {
while (runnable) {
try {
semaphore.acquire();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println(e.getMessage());
}
if (!runnable) break;

job();
}
}

public void eat() {
semaphore.release();
}

public void awaitDone() throws InterruptedException {
doneSemaphore.acquire();
}

public void stop() {
runnable = false;
semaphore.release();
}

private void job() {
r.run();
eaten.getAndIncrement();
doneSemaphore.release();
}
}
17 changes: 17 additions & 0 deletions src/main/java/org/labs/Spoon.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.labs;

public class Spoon {
volatile private boolean onTable = true;

public boolean isOnTable() {
return onTable;
}

public void pickMe() {
onTable = false;
}

public void putMeBack() {
onTable = true;
}
}
149 changes: 149 additions & 0 deletions src/main/java/org/labs/Table.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package org.labs;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class Table {
private final int dishes;
private final int programmers;
private final List<Spoon> spoons;
private final Lock lock = new ReentrantLock();
private final Condition spoonsAvailable = lock.newCondition();
private final List<Programmer> programmerList;
private final List<Thread> programmerThreads;
private final ThreadPoolExecutor waiterPoolExecutor;

public Table(int programmers, int waiters, int dishes, Runnable r) throws IllegalArgumentException {
this.dishes = dishes;
this.programmers = programmers;

if (programmers < 2 || waiters < 1)
throw new IllegalArgumentException();

if (programmers < waiters) waiters = programmers;

waiterPoolExecutor = new ThreadPoolExecutor(
waiters, waiters,
100, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(dishes));

spoons = Stream
.generate(Spoon::new)
.limit(programmers)
.collect(Collectors.toCollection(ArrayList::new));

programmerList = IntStream
.range(0, programmers)
.mapToObj(i -> new Programmer(r))
.limit(programmers)
.toList();

programmerThreads = IntStream
.range(0, programmers)
.mapToObj(i -> new Thread(programmerList.get(i), "" + i))
.toList();
}

public Table(int programmers, int waiters, int dishes) {
this(programmers, waiters, dishes, () -> {});
}

public void run() {
programmerThreads.forEach(Thread::start);

for (int i = 0; i < dishes; i++) {
waiterPoolExecutor.execute(() -> {
lock.lock();
int n = getStarve();
Programmer p = programmerList.get(n);
p.setHasWaiter(true);
lock.unlock();

Spoon leftSpoon = spoons.get(n);
Spoon rightSpoon = spoons.get((n + 1) % programmers);

lock.lock();
try {
while (!(leftSpoon.isOnTable() && rightSpoon.isOnTable())) {
try {
spoonsAvailable.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
p.setHasWaiter(false);
return;
}
}
leftSpoon.pickMe();
rightSpoon.pickMe();
} finally {
lock.unlock();
}

p.eat();
try {
p.awaitDone();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}

lock.lock();
try {
leftSpoon.putMeBack();
rightSpoon.putMeBack();
spoonsAvailable.signalAll();
} finally {
lock.unlock();
}

p.setHasWaiter(false);
});
}

waiterPoolExecutor.shutdown();
try {
waiterPoolExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}

for (var p : programmerList) p.stop();

for (var t : programmerThreads) {
try {
t.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}

public List<Programmer> getInfo() {
return programmerList;
}

private int getStarve() {
int result = 0;
int min = Integer.MAX_VALUE;

for (int i = 0; i < programmerList.size(); i++) {
if (programmerList.get(i).getHasWaiter().get()) continue;

int temp = programmerList.get(i).getEaten().get();
if (temp < min) {
min = temp;
result = i;
}
}

return result;
}
}
3 changes: 3 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
amount_around_table=7
dishes=1000000
waiters=2
Loading