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 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
17 changes: 17 additions & 0 deletions src/main/java/org/labs/Kitchen.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.labs;

import java.util.concurrent.atomic.AtomicLong;

public class Kitchen {

private final AtomicLong foodPortions;

public Kitchen(long foodPortionCount) {
this.foodPortions = new AtomicLong(foodPortionCount);
}

public boolean cookFood() {
return foodPortions.getAndDecrement() > 0;
}

}
54 changes: 53 additions & 1 deletion src/main/java/org/labs/Main.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,59 @@
package org.labs;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.stream.LongStream;

public class Main {

private static final Duration talkDuration = Duration.ofNanos(100);
private static final long programmerCount = 3;
private static final long foodCount = 10_000;
private static final long waiterCount = 3;

public static void main(String[] args) {
System.out.println("Hello, World!");
Kitchen kitchen = new Kitchen(foodCount);
List<Waiter> waiters = LongStream.range(0, waiterCount)
.mapToObj(num -> new Waiter(kitchen))
.toList();
List<Spoon> spoons = LongStream.range(0, programmerCount)
.mapToObj(num -> new Spoon())
.toList();
List<Programmer> programmers = LongStream.range(0, programmerCount)
.mapToObj(num -> new Programmer(
waiters,
spoons.get((int) (num % spoons.size())),
spoons.get((int) ((num + 1) % spoons.size())),
new Random(),
talkDuration
))
.toList();
List<Thread> threads = new ArrayList<>();
for (Programmer prog : programmers) {
Thread t = new Thread(prog);
threads.add(t);
t.start();
}
threads.forEach((t) -> {
try {
t.join();
} catch (InterruptedException e) {
throw new RuntimeException("Main thread interrupted");
}
});
long waiterSum = 0;
for (var waiter : waiters) {
System.out.printf("Waiter deliver %d portions\n", waiter.getDeliveredFoodCount());
waiterSum += waiter.getDeliveredFoodCount();
}
System.out.printf("Waiter total deliver %d\n", waiterSum);
long programmerEat = 0;
for (var programmer : programmers) {
System.out.printf("Programmer eat %d portions\n", programmer.getTotalEat());
programmerEat += programmer.getTotalEat();
}
System.out.printf("Programmers total eat %d portions\n", programmerEat);
}
}
50 changes: 50 additions & 0 deletions src/main/java/org/labs/Plate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.labs;

import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Plate {

private static final Logger logger = Logger.getLogger("Plate");

private final AtomicReference<FoodStatus> foodStatus = new AtomicReference<>(FoodStatus.NO_FOOD);

public enum FoodStatus {
NO_FOOD,
AWAIT_FOOD,
HAVE_FOOD,
NO_MORE_FOOD
}

public FoodStatus getStatus() {
return foodStatus.get();
}

public void startAwaitFood() {
var exchangeResult = foodStatus.compareAndExchange(FoodStatus.NO_FOOD, FoodStatus.AWAIT_FOOD);
if (exchangeResult != FoodStatus.NO_FOOD) {
logger.log(Level.WARNING, "Start await food in not expected status " + exchangeResult);
}
}

public void receiveFood() {
var exchangeResult = foodStatus.compareAndExchange(FoodStatus.AWAIT_FOOD, FoodStatus.HAVE_FOOD);
if (exchangeResult != FoodStatus.AWAIT_FOOD) {
logger.log(Level.WARNING, "Receive food in not expected status " + exchangeResult);
}
}

public void noMoreFood() {
var exchangeResult = foodStatus.compareAndExchange(FoodStatus.AWAIT_FOOD, FoodStatus.NO_MORE_FOOD);
if (exchangeResult != FoodStatus.AWAIT_FOOD) {
logger.log(Level.WARNING, "Mark status no more food in not expected status " + exchangeResult);
}
}

public boolean consumeFood() {
var exchangeResult = foodStatus.compareAndExchange(FoodStatus.HAVE_FOOD, FoodStatus.NO_FOOD);
return exchangeResult == FoodStatus.HAVE_FOOD;
}

}
95 changes: 95 additions & 0 deletions src/main/java/org/labs/Programmer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package org.labs;

import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CompletionException;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Programmer implements Runnable {

private static final Logger logger = Logger.getLogger("Programmer");

private final Duration talkDuration;

private final AtomicLong totalEat = new AtomicLong();

private final List<Waiter> waiters;
private final Random random;
private final Plate plate = new Plate();
private final List<Spoon> spoons;

public Programmer(Collection<Waiter> waiters, Spoon leftSpoon, Spoon rightSpoon, Random random, Duration talkDuration) {
if (waiters instanceof List<Waiter> waitersList) {
this.waiters = waitersList;
} else {
this.waiters = List.copyOf(waiters);
}
this.talkDuration = talkDuration;
spoons = leftSpoon.compareTo(rightSpoon) < 0 ?
List.of(leftSpoon, rightSpoon) :
List.of(rightSpoon, leftSpoon);
this.random = random;
}

@Override
public void run() {
while (!Thread.interrupted()) {
eat();
talk();
}
}

public long getTotalEat() {
return totalEat.get();
}

private void eat() {
switch (plate.getStatus()) {
case HAVE_FOOD -> {
synchronized (spoons.get(0)) {
synchronized (spoons.get(1)) {
boolean consumed = plate.consumeFood();
if (consumed) {
totalEat.incrementAndGet();
}
}
}
}
case NO_FOOD -> callWaiter();
case NO_MORE_FOOD -> Thread.currentThread().interrupt();
case AWAIT_FOOD -> {}
}
}

private void talk() {
try {
Thread.sleep(talkDuration);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}

private void callWaiter() {
var waiter = waiters.get(random.nextInt(waiters.size()));
plate.startAwaitFood();
waiter.bringFood()
.thenAccept(this::receiveFood)
.exceptionally(e -> {
logger.log(Level.SEVERE, "Fail to bring food", e);
throw new CompletionException(e);
});
}

private void receiveFood(boolean isSuccess) {
if (isSuccess) {
plate.receiveFood();
} else {
plate.noMoreFood();
}
}

}
19 changes: 19 additions & 0 deletions src/main/java/org/labs/Spoon.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.labs;

import java.util.concurrent.atomic.AtomicLong;

public class Spoon implements Comparable<Spoon> {

private static final AtomicLong currentSpoonId = new AtomicLong();

private final long id;

public Spoon() {
this.id = currentSpoonId.getAndIncrement();
}

@Override
public int compareTo(Spoon o) {
return Long.compare(id, o.id);
}
}
30 changes: 30 additions & 0 deletions src/main/java/org/labs/Waiter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.labs;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicLong;

public class Waiter {

private final Kitchen kitchen;
private final AtomicLong deliveredFood = new AtomicLong();

public Waiter(Kitchen kitchen) {
this.kitchen = kitchen;
}

public long getDeliveredFoodCount() {
return deliveredFood.get();
}

public CompletableFuture<Boolean> bringFood() {
return CompletableFuture
.supplyAsync(kitchen::cookFood)
.thenApply(food -> {
if (food) {
deliveredFood.incrementAndGet();
}
return food;
});
}

}
37 changes: 37 additions & 0 deletions src/test/java/org/labs/BaseTests.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.labs;

import org.junit.jupiter.api.RepeatedTest;

import java.time.Duration;

public class BaseTests {

@RepeatedTest(20)
public void baseTest() {
TestTable testTable = new TestTable(1_000, 3, 7, Duration.ZERO);
testTable.run();
testTable.testResult();
}

@RepeatedTest(20)
public void nonZeroDurationTest() {
TestTable testTable = new TestTable(1_000, 3, 7, Duration.ofNanos(100));
testTable.run();
testTable.testResult();
}

@RepeatedTest(20)
public void manyProgrammerTest() {
TestTable testTable = new TestTable(1_000, 2, 100, Duration.ZERO);
testTable.run();
testTable.testResult();
}

@RepeatedTest(20)
public void manyWaitersTest() {
TestTable testTable = new TestTable(1_000, 1000, 2, Duration.ZERO);
testTable.run();
testTable.testResult();
}

}
68 changes: 68 additions & 0 deletions src/test/java/org/labs/TestTable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package org.labs;

import org.junit.jupiter.api.Assertions;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.stream.LongStream;

public class TestTable {

private final long foodCount;

private final List<Waiter> waiters;
private final List<Spoon> spoons;
private final List<Programmer> programmers;

public TestTable(long foodCount, long waiterCount, long programmerCount, Duration talkDuration) {
this.foodCount = foodCount;
Kitchen kitchen = new Kitchen(foodCount);
waiters = LongStream.range(0, waiterCount)
.mapToObj(num -> new Waiter(kitchen))
.toList();
spoons = LongStream.range(0, programmerCount)
.mapToObj(num -> new Spoon())
.toList();
programmers = LongStream.range(0, programmerCount)
.mapToObj(num -> new Programmer(
waiters,
spoons.get((int) (num % spoons.size())),
spoons.get((int) ((num + 1) % spoons.size())),
new Random(),
talkDuration
))
.toList();
}

public void run() {
List<Thread> threads = new ArrayList<>();
for (Programmer prog : programmers) {
Thread t = new Thread(prog);
threads.add(t);
t.start();
}
threads.forEach((t) -> {
try {
t.join();
} catch (InterruptedException e) {
throw new RuntimeException("Main thread interrupted");
}
});
}

public void testResult() {
Assertions.assertEquals(
foodCount,
programmers.stream().mapToLong(Programmer::getTotalEat).sum(),
"Programmers eat wrong count"
);
Assertions.assertEquals(
foodCount,
waiters.stream().mapToLong(Waiter::getDeliveredFoodCount).sum(),
"Waiters deliver wrong count of food"
);
}

}