Skip to content
Open
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
11 changes: 10 additions & 1 deletion src/main/java/org/labs/Main.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
package org.labs;

import org.labs.dining.DiningProgrammersWorld;

import java.util.Arrays;

public class Main {

public static void main(String[] args) {
System.out.println("Hello, World!");
try {
new DiningProgrammersWorld().startDining(1_000, 3, 7);
} catch (InterruptedException e) {
System.out.println("Ooopps... " + Arrays.toString(e.getStackTrace()));
}
}
}
46 changes: 46 additions & 0 deletions src/main/java/org/labs/dining/DiningProgrammersWorld.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.labs.dining;

import org.labs.dining.items.Programmer;
import org.labs.dining.items.Spoon;

public class DiningProgrammersWorld {

private static final int DEFAULT_PORTIONS_AMOUNT = 1_000_000;
private static final int DEFAULT_WAITERS_AMOUNT = 2;
private static final int DEFAULT_PROGRAMMERS_AMOUNT = 5;

private Programmer[] programmers;

public Programmer[] getProgrammers() {
return programmers;
}

public void startDining(
Integer portionsAmount,
Integer waitersAmount,
Integer programmersAmount
) throws InterruptedException {
int portionsAmountToInitialize = portionsAmount == null ? DEFAULT_PORTIONS_AMOUNT : portionsAmount;
int waitersAmountToInitialize = waitersAmount == null ? DEFAULT_WAITERS_AMOUNT : waitersAmount;
int programmersAmountToInitialize = programmersAmount == null ? DEFAULT_PROGRAMMERS_AMOUNT : programmersAmount;

SharedContext.initialize(portionsAmountToInitialize, waitersAmountToInitialize, programmersAmountToInitialize);
programmers = new Programmer[programmersAmountToInitialize];

for (int i = 0; i < programmersAmountToInitialize; i++) {
Spoon leftSpoon = SharedContext.getSpoon(i);
Spoon rightSpoon = SharedContext.getSpoon((i + 1) % programmersAmountToInitialize);

String programmerName = "Programmer_" + i;
Programmer currentProgrammer = i == programmersAmountToInitialize - 1
? new Programmer(programmerName, rightSpoon, leftSpoon)
: new Programmer(programmerName, leftSpoon, rightSpoon);
currentProgrammer.start();
programmers[i] = currentProgrammer;
}

for (int i = 0; i < programmersAmountToInitialize; i++) {
programmers[i].join();
}
}
}
42 changes: 42 additions & 0 deletions src/main/java/org/labs/dining/SharedContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.labs.dining;

import org.labs.dining.items.Spoon;
import org.labs.dining.items.Waiter;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class SharedContext {

private static Waiter[] waiters;
private static Spoon[] spoons;
private static BlockingQueue<Waiter> waiterBlockingQueue;

public static Spoon getSpoon(int atIndex) {
return spoons[atIndex];
}

public static BlockingQueue<Waiter> getWaiterBlockingQueue() {
return waiterBlockingQueue;
}

public static void initialize(
int portionsAmount,
int waitersAmount,
int spoonsAmount
) throws InterruptedException {
Waiter.setPortionsAmount(portionsAmount);

SharedContext.waiters = new Waiter[waitersAmount];
SharedContext.spoons = new Spoon[spoonsAmount];
SharedContext.waiterBlockingQueue = new ArrayBlockingQueue<>(waitersAmount);
Copy link

Choose a reason for hiding this comment

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

Почему вы остановили свой выбор на этой структуре? Как у вас обстоят дела с равномерностью накормленности? Чем она обеспечивается?

Copy link
Author

Choose a reason for hiding this comment

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

Для официантов эта структура была выбрана по той причине, что в моём понимании она достаточно точно описывает модель поведения официантов - программист "забирает" официанта из очереди, чтобы он принёс ему еду, затем официант опять помещается в очередь, готовый, когда будет его время, опять "сходить" за едой

Copy link
Author

Choose a reason for hiding this comment

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

Равномерность накормленности никак не регулируется мной, упустил этот момент...


for (int i = 0; i < waitersAmount; i++) {
SharedContext.waiters[i] = new Waiter();
SharedContext.waiterBlockingQueue.put(SharedContext.waiters[i]);
}
for (int i = 0; i < spoonsAmount; i++) {
SharedContext.spoons[i] = new Spoon();
}
}
}
79 changes: 79 additions & 0 deletions src/main/java/org/labs/dining/items/Programmer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package org.labs.dining.items;

import org.labs.dining.SharedContext;

public class Programmer extends Thread {

private final Spoon leftSpoon;
private final Spoon rightSpoon;

private int portionsConsumed = 0;

public Programmer(String programmerName, Spoon leftSpoon, Spoon rightSpoon) {
super(programmerName);
this.leftSpoon = leftSpoon;
this.rightSpoon = rightSpoon;
}

public int getPortionsConsumed() {
return portionsConsumed;
}

/**
* Любое действие выводится в консоль, поэтому управление "длительностью" действия
* происходит здесь - в одном месте
* <p>
* Выбирается случайное время для задержки, печатается действия и поток "засыпает" на выбранное время
*/
private synchronized void printMessage(String message) throws InterruptedException {
int ms = (int) (Math.random() * 100);
System.out.println(Thread.currentThread().getName() + " " + message + " for: " + ms + "ms");
Thread.sleep(ms);
}

public void trashTalk() throws InterruptedException {
printMessage("is trash-talking");
}

public boolean requestPortion() throws InterruptedException {
Waiter waiter = SharedContext.getWaiterBlockingQueue().take();
boolean portionTaken = waiter.tryTakePortion();
SharedContext.getWaiterBlockingQueue().put(waiter);
return portionTaken;
}

public void takeSpoonsAndEat() throws InterruptedException {
synchronized (leftSpoon) {
printMessage("takes left spoon");
synchronized (rightSpoon) {
printMessage("takes right spoon");
eat();
}
}
}

private void eat() throws InterruptedException {
printMessage("is eating");
this.portionsConsumed++;
}

public void putSpoons() throws InterruptedException {
printMessage("releasing spoons");
}

@Override
public void run() {
try {
while (true) {
trashTalk();
boolean portionTaken = requestPortion();
if (!portionTaken) break;
takeSpoonsAndEat();
putSpoons();
}
System.out.println("Done " + this.getName() + " with consumed: " + this.portionsConsumed);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
4 changes: 4 additions & 0 deletions src/main/java/org/labs/dining/items/Spoon.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package org.labs.dining.items;

public class Spoon {
}
17 changes: 17 additions & 0 deletions src/main/java/org/labs/dining/items/Waiter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.labs.dining.items;

import java.util.concurrent.atomic.AtomicInteger;

public class Waiter {

private static AtomicInteger portionsAmount;

public static void setPortionsAmount(int portionsAmount) {
Waiter.portionsAmount = new AtomicInteger(portionsAmount);
}

public boolean tryTakePortion() {
int decrementedPortionsAmount = portionsAmount.getAndDecrement();
return decrementedPortionsAmount > 0;
}
}
78 changes: 78 additions & 0 deletions src/test/java/dining/DiningProgrammersWorldTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package dining;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.labs.dining.DiningProgrammersWorld;
import org.labs.dining.items.Programmer;

import java.util.Arrays;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class DiningProgrammersWorldTest {

private static final int SMALL_PORTIONS_AMOUNT_TO_SIMULATE = 1000;
private static final int MEDIUM_PORTIONS_AMOUNT_TO_SIMULATE = 10_000;
private static final int BIG_PORTIONS_AMOUNT_TO_SIMULATE = 100_000;
private static final int LARGE_PORTIONS_AMOUNT_TO_SIMULATE = 1_000_000;

private static final int WAITERS_AMOUNT_TO_SIMULATE = 2;

private static final int PROGRAMMERS_AMOUNT_TO_SIMULATE = 5;

public record DiningProgrammersWorldArgument(int portionsAmount, int waitersAmount, int programmersAmount){}

private static Stream<Arguments> provideArguments() {
return Stream.of(
Arguments.of(new DiningProgrammersWorldArgument(
SMALL_PORTIONS_AMOUNT_TO_SIMULATE,
WAITERS_AMOUNT_TO_SIMULATE,
PROGRAMMERS_AMOUNT_TO_SIMULATE
)
),
Arguments.of(new DiningProgrammersWorldArgument(
MEDIUM_PORTIONS_AMOUNT_TO_SIMULATE,
WAITERS_AMOUNT_TO_SIMULATE,
PROGRAMMERS_AMOUNT_TO_SIMULATE
)
),
Arguments.of(new DiningProgrammersWorldArgument(
BIG_PORTIONS_AMOUNT_TO_SIMULATE,
WAITERS_AMOUNT_TO_SIMULATE,
PROGRAMMERS_AMOUNT_TO_SIMULATE
)
)
);
}

@Test
public void testNoDeadlocks() throws InterruptedException {
new DiningProgrammersWorld().startDining(
LARGE_PORTIONS_AMOUNT_TO_SIMULATE,
WAITERS_AMOUNT_TO_SIMULATE,
PROGRAMMERS_AMOUNT_TO_SIMULATE
);
}

@ParameterizedTest
@MethodSource("provideArguments")
public void testNoRaceCondition(DiningProgrammersWorldArgument argument) throws InterruptedException {
DiningProgrammersWorld diningProgrammersWorld = new DiningProgrammersWorld();
diningProgrammersWorld.startDining(
argument.portionsAmount(),
argument.waitersAmount(),
argument.programmersAmount()
);

Programmer[] programmers = diningProgrammersWorld.getProgrammers();

int consumedPortionsSum = Arrays.stream(programmers)
.map(Programmer::getPortionsConsumed)
.reduce(0, Integer::sum);

assertEquals(argument.portionsAmount(), consumedPortionsSum);
}
}