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
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ repositories {
dependencies {
testImplementation(platform("org.junit:junit-bom:5.10.0"))
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.openjdk.jmh:jmh-core:1.37")
testImplementation("org.openjdk.jmh:jmh-generator-annprocess:1.37")
}

tasks.test {
Expand Down
Empty file modified gradlew
100644 → 100755
Empty file.
9 changes: 8 additions & 1 deletion src/main/java/org/labs/Main.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package org.labs;

public class Main {
public static final boolean DEBUG = true;

public static void main(String[] args) {
System.out.println("Hello, World!");
Restaurant restaurant = new Restaurant(100, 2, 1_000_000, false);
restaurant.start();
restaurant.join();
if (DEBUG) {
System.out.println("Нечестность = " + restaurant.getUnfairness());
}
}
}
71 changes: 71 additions & 0 deletions src/main/java/org/labs/Programmer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package org.labs;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

public class Programmer extends Thread {
private static final AtomicInteger ID_CNT = new AtomicInteger();

private final BlockingQueue<Waiter> waiters;
private final Spoon first;
private final Spoon second;
private volatile boolean hasSoup;
private long ate = 0;

public Programmer(BlockingQueue<Waiter> waiters, Spoon leftSpoon, Spoon rightSpoon) {
setName(Programmer.class.getName() + '.' + ID_CNT.incrementAndGet());
setDaemon(true);
this.waiters = waiters;
this.hasSoup = true;
if (leftSpoon.compareTo(rightSpoon) > 0) {
this.first = rightSpoon;
this.second = leftSpoon;
} else {
this.first = leftSpoon;
this.second = rightSpoon;
}
}

@Override
public void run() {
while (true) {
if (hasSoup) {
eat();
} else {
requestMoreSoup();
}
}
}

public synchronized void addMoreSoup() {
this.hasSoup = true;
notify();
}

private void eat() {
first.lock();
second.lock();
hasSoup = false;
if (Main.DEBUG) {
ate++;
}
first.unlock();
second.unlock();
}

private synchronized void requestMoreSoup() {
try {
this.waiters.take().requestSoup(this);
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

public long getAte() {
if (!Main.DEBUG) {
throw new UnsupportedOperationException("Debug is not enabled!");
}
return ate;
}
}
40 changes: 40 additions & 0 deletions src/main/java/org/labs/Restaurant.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.labs;

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

public class Restaurant {
private final Table table;
private final AtomicInteger portionsLeft;
private final BlockingQueue<Waiter> waitersQueue;

public Restaurant(int programmersCnt, int waitersCnt, int portions, boolean fairLock) {
this.portionsLeft = new AtomicInteger(portions - programmersCnt);
this.waitersQueue = new ArrayBlockingQueue<>(waitersCnt, fairLock);
for (int i = 0; i < waitersCnt; i++) {
try {
this.waitersQueue.put(new Waiter(this.portionsLeft, this.waitersQueue));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

this.table = new Table(this.waitersQueue, programmersCnt, fairLock);
}

public void start() {
for (Waiter waiter : waitersQueue) {
waiter.start();
}
table.start();
}

public void join() {
while (portionsLeft.get() > 0) ;
}

public double getUnfairness() {
return table.getUnfairness();
}
}
25 changes: 25 additions & 0 deletions src/main/java/org/labs/Spoon.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.labs;

import java.util.concurrent.locks.ReentrantLock;

public class Spoon extends ReentrantLock implements Comparable<Spoon> {
private final int id;

public Spoon(int id) {
this(id, true);
}

public Spoon(int id, boolean fair) {
super(fair);
this.id = id;
}

public int getId() {
return id;
}

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

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;

public class Table {
final private List<Programmer> programmers;

public Table(BlockingQueue<Waiter> waiters, int programmersCnt, boolean fairLock) {
programmers = new ArrayList<>(programmersCnt);
List<Spoon> spoons = new ArrayList<>(programmersCnt);
for (int i = 0; i < programmersCnt; i++) {
if (i % 2 == 0) {
spoons.add(new Spoon(i / 2, fairLock));
} else {
spoons.add(new Spoon(programmersCnt - (i / 2), fairLock));
}
}
for (int i = 0; i < programmersCnt; i++) {
Spoon left = spoons.get(i);
Spoon right = spoons.get((i + 1) % programmersCnt);
programmers.add(new Programmer(waiters, left, right));
}
}

public void start() {
for (Programmer programmer : this.programmers) {
programmer.start();
}
}

public double getUnfairness() {
double mean = 0;
for (Programmer programmer: programmers) {
mean += ((double) programmer.getAte()) / programmers.size();
}
double unfairness = 0;
for (Programmer programmer: programmers) {
unfairness += Math.abs(programmer.getAte() - mean) / (programmers.size());
}
return unfairness / mean;
}
}
58 changes: 58 additions & 0 deletions src/main/java/org/labs/Waiter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.labs;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;

public class Waiter extends Thread {
private final static Logger LOG = Logger.getLogger(Waiter.class.getName());
private final static AtomicInteger ID_CNT = new AtomicInteger();
private final BlockingQueue<Programmer> waitingProgrammers = new SynchronousQueue<>();
private final AtomicInteger portionLeft;
private final BlockingQueue<Waiter> waiters;
private int delivered = 0;

public Waiter(AtomicInteger portionLeft, BlockingQueue<Waiter> waiters) {
setName(Waiter.class.getName() + '.' + ID_CNT.incrementAndGet());
this.portionLeft = portionLeft;
this.waiters = waiters;
}

@Override
public void run() {
while (true) {
try {
Programmer programmer = waitingProgrammers.take();
if (tryToTakeSoup()) {
programmer.addMoreSoup();
if (Main.DEBUG) {
delivered++;
}
} else {
LOG.info("Официант покинул работу");
return;
}
waiters.put(this);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}

public void requestSoup(Programmer programmer) throws InterruptedException {
waitingProgrammers.put(programmer);
}

private boolean tryToTakeSoup() {
while (true) {
int currentPortionsLeft = portionLeft.get();
if (currentPortionsLeft <= 0) {
return false;
}
if (portionLeft.compareAndSet(currentPortionsLeft, currentPortionsLeft - 1)) {
return true;
}
}
}
}
112 changes: 112 additions & 0 deletions src/test/java/org/labs/RestaurantPerformanceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package org.labs;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.jupiter.params.provider.CsvSource;

import java.util.logging.Level;
import java.util.logging.Logger;


@DisplayName("Restaurant Performance Tests")
public class RestaurantPerformanceTest {

@BeforeAll
public static void disableLogging() {
Logger rootLogger = Logger.getLogger("");
rootLogger.setLevel(Level.OFF);
}

@ParameterizedTest
@ValueSource(ints = {2, 4, 8, 16, 32})
@DisplayName("Тестирование производительности при различном количестве программистов")
public void testProgrammerCountPerformance(int programmersCnt) {
int waitersCnt = 2;
boolean fairLock = true;

long executionTime = measurePerformance(programmersCnt, waitersCnt, fairLock);

System.out.printf("Программистов: %d, Официантов: %d, Fair Lock: %s%n", programmersCnt, waitersCnt, fairLock);
System.out.printf("Время выполнения: %dмс%n", executionTime);
}

@ParameterizedTest
@ValueSource(ints = {1, 2, 4, 8})
@DisplayName("Тестирование производительности при различном количестве официантов")
public void testWaiterCountPerformance(int waitersCnt) {
int programmersCnt = 16;
boolean fairLock = true;

long executionTime = measurePerformance(programmersCnt, waitersCnt, fairLock);

System.out.printf("Программистов: %d, Официантов: %d, Fair Lock: %s%n", programmersCnt, waitersCnt, fairLock);
System.out.printf("Время выполнения: %dms%n", executionTime);
}

@ParameterizedTest
@CsvSource({"4, 2, true", "4, 2, false", "8, 2, true", "8, 2, false", "16, 4, true", "16, 4, false"})
@DisplayName("Тестирование производительности при различных типах блокировок")
public void testFairVsUnfairLockPerformance(int programmersCnt, int waitersCnt, boolean fairLock) {
long executionTime = measurePerformance(programmersCnt, waitersCnt, fairLock);

System.out.printf("Программистов: %d, Официантов: %d, Fair Lock: %s%n", programmersCnt, waitersCnt, fairLock);
System.out.printf("Время выполнения: %dмс%n", executionTime);
}

@Test
@DisplayName("Стресс-тест с высокой конкурентостью")
public void stressTest() {
int programmersCnt = 64;
int waitersCnt = 16;
boolean fairLock = true;

long executionTime = measurePerformance(programmersCnt, waitersCnt, fairLock);

System.out.printf("Стресс-тест - Программистов: %d, Официантов: %d%n", programmersCnt, waitersCnt);
System.out.printf("Время выполнения: %dms%n", executionTime);
}

private long measurePerformance(int programmersCnt, int waitersCnt, boolean fairLock) {
final int warmupIterations = 2;
final int measurementIterations = 4;

// Прогрев
System.out.printf("Прогрев (%d итераций)...%n", warmupIterations);
for (int i = 0; i < warmupIterations; i++) {
runSingleIteration(programmersCnt, waitersCnt, fairLock);
}

// Измерения для усреднения
System.out.printf("Измерение производительности (%d итераций)...%n", measurementIterations);
long totalTime = 0;
for (int i = 0; i < measurementIterations; i++) {
long iterationTime = runSingleIteration(programmersCnt, waitersCnt, fairLock);
totalTime += iterationTime;
System.out.printf("\tИтерация %d: %dмс%n", i + 1, iterationTime);
}

long averageTime = totalTime / measurementIterations;
System.out.printf("Среднее время: %d мс\n", averageTime);

return averageTime;
}

private long runSingleIteration(int programmersCnt, int waitersCnt, boolean fairLock) {
long startTime = System.currentTimeMillis();

Restaurant restaurant = new Restaurant(programmersCnt, waitersCnt, 100_000, fairLock);
restaurant.start();
restaurant.join();

System.out.println("Нечестность = " + restaurant.getUnfairness());
assert !fairLock || restaurant.getUnfairness() < 0.1;
long endTime = System.currentTimeMillis();

return endTime - startTime;
}

}