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
3 changes: 3 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions .idea/.name

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAdder;
import java.util.Arrays;

public class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
/**
* Точка входа в программу. Здесь задаются параметры выполняемыой программы (см. TaskStatic), вызывается выполнение программы,
* собираются результаты и выводятся в консоль
* @param args Не используется, так как статика задаётся хард-кодом внутри самого метода
* @throws InterruptedException Просто висит
*/
public static void main(String[] args) throws InterruptedException {
TaskStatic ts = new TaskStatic( //
1, // programmersCount
1, // waitersCount
20, // rationsCount
1, // eatTimeMillisMin
3, // eatTimeMillisMax
1, // talkTimeMillisMin
3 // talkTimeMillisMax
);

Result r = startDinner(ts);

Map<Integer, Long> rationEatenStats = new LinkedHashMap<>();
for (int i = 0; i < r.eatenByProgrammer().length; i++) {
rationEatenStats.put(i, r.eatenByProgrammer()[i]);
}

System.out.println("Rations eaten by each programmer - " + rationEatenStats);
System.out.println("Rations eaten totally - " + r.totalEaten());
System.out.println("Rations left - " + r.rationsLeft());
}

/**
* Основной выполняющий метод, вызывается из мейна. Здесь задаются необходимые объекты, по типу ложек и шкафа с едой
* и происходит само выполнение программы, с досугом рационного возъедания и болтовнёй
* @param ts Настройки программы (статика)
* @return Результат обеда
* @throws InterruptedException Зачем-то пробрасывается
*/
public static Result startDinner(TaskStatic ts) throws InterruptedException {
RationShelf rs = new RationShelf(ts.getRationsCount());
Waiters w = new Waiters(ts.getWaitersCount(), ts.getProgrammersCount());

int programmersCount = ts.getProgrammersCount();

LongAdder[] la = new LongAdder[programmersCount];
Arrays.setAll(la, _ -> new LongAdder());

Spoon[] s = new Spoon[programmersCount];
for (int i = 0; i < programmersCount; i++) {
s[i] = new Spoon(i);
}

try (ExecutorService programmersExec = Executors.newVirtualThreadPerTaskExecutor()) {
List<Programmer> programmersList = new ArrayList<>(programmersCount);

try {
for (int i = 0; i < programmersCount; i++) {
Spoon l = s[i];
Spoon r = s[(i + 1) % programmersCount];
Programmer p = new Programmer(l, r, rs, w, ts, la[i]);
programmersList.add(p);
programmersExec.submit(p);
}

while (rs.getLeftRations().get() > 0) {
Thread.sleep(TaskStatic.ProjectConfig.WAIT_UNTIL_RATION_SHELF_IS_EMPTY);
}
} finally {
for (Programmer p : programmersList) {
p.stop();
}
programmersExec.shutdownNow();
}
}

long[] rationsEaten = new long[programmersCount];
long total = 0;
for (int i = 0; i < programmersCount; i++) {
rationsEaten[i] = la[i].sum();
total += rationsEaten[i];
}
long left = rs.getLeftRations().get();

return new Result(rationsEaten, total, left);
}

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

import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.LongAdder;

/**
* Класс программистов, которые будут конкурентно есть еду
*/
public class Programmer implements Runnable {
private final Spoon leftSpoon;
private final Spoon rightSpoon;
private final RationShelf rs;
private final Waiters w;
private final TaskStatic ts;
private final LongAdder stats;
private volatile boolean running = true;

public Programmer(
Spoon leftSpoon,
Spoon rightSpoon,
RationShelf rationShelf,
Waiters waiters,
TaskStatic taskStatic,
LongAdder stats
) {
this.leftSpoon = leftSpoon;
this.rightSpoon = rightSpoon;
this.rs = rationShelf;
this.w = waiters;
this.ts = taskStatic;
this.stats = stats;
}

/**
* Метод поедания еды программистом, здесь происходит периодическая болтовня, взятие ложек и поедание рационов
*/
@Override
public void run() {
while (running) {
try {
talkAboutTeachers();
w.takeWaiter(stats);
try {
TwoSpoons sp = checkSpoons();
if (tryTakeSpoons(sp)) {
try {
if (!rs.takeRation()) {
running = false;
continue;
}
eatRation();
stats.increment();
} finally {
returnSpoons(sp);
}
}
} finally {
w.letWaiterGo();
}
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
} catch (Throwable t) {
System.out.println("[programmer.run] throwable error - " + t.getMessage());
}
}
}

/**
* Метод для взятия ложек, проверяем можем ли мы взять обе ложки в каком-то временном интервале.
* Если нет, то возвращаем обе ложки
* @param sp Набор ложек, которые мы можем взять
* @return исход взятия обеих ложек (true если взяли обе
* @throws InterruptedException
*/
private boolean tryTakeSpoons(TwoSpoons sp) throws InterruptedException {
long waitTime = TaskStatic.ProjectConfig.PROGRAMMER_WAIT_TIME;
if (!sp.first.useWithTimeOut(waitTime)) {
return false;
}
boolean gotSecond;
try {
gotSecond = sp.second.useWithTimeOut(waitTime);
if (!gotSecond) {
sp.first.put();
return false;
}
return true;
} catch (InterruptedException ie) {
sp.first.put();
throw ie;
}
}

/**
* Метод для болтовни, программист будет болтать какое-то случайное время, в рамках заданных настроек
* @throws InterruptedException
*/
private void talkAboutTeachers() throws InterruptedException {
action(ts.getTalkTimeMillisMin(), ts.getTalkTimeMillisMax());
}

/**
* То же самое, что и talkAboutTeachers, только для поедания еды (работает с другим временем)
* @throws InterruptedException
*/
private void eatRation() throws InterruptedException {
action(ts.getEatTimeMillisMin(), ts.getEatTimeMillisMax());
}

/**
* Метод проверки, что программист будет брать две ложки, которые лежат рядом с ним
* @return Объект-хелпер с двумя ложками, которые может взять программист
*/
private TwoSpoons checkSpoons() {
if (leftSpoon.getSpoonIndex() < rightSpoon.getSpoonIndex()) {
return new TwoSpoons(leftSpoon, rightSpoon);
} else {
return new TwoSpoons(rightSpoon, leftSpoon);
}
}

/**
* Метод выполнения каког-то действия - есть или болтать
* @param minMs Минимальное время выполнения действия
* @param maxMs Максимальное время выполнения действия
* @throws InterruptedException
*/
private void action(long minMs, long maxMs) throws InterruptedException {
long span = Math.max(0, maxMs - minMs);

long ms;
if (span == 0) {
ms = minMs;
} else {
ms = minMs + ThreadLocalRandom.current().nextLong(span);
}

if (ms > 0) {
Thread.sleep(ms);
}
}

/**
* Возможность позвать свободного оффицианта и взять у него рацион
* @return Исход возможности взятия рациона
* @throws InterruptedException
*/
private boolean takePortionWithWaiter() throws InterruptedException {
w.takeWaiter(stats);
try {
if (!rs.takeRation()) {
return false;
}
eatRation();
stats.increment();
return true;
} finally {
w.letWaiterGo();
}
}

/**
* Возврат обеих ложек
* @param sp Набор ложек конкретного программиста
*/
private void returnSpoons(TwoSpoons sp) {
sp.second.put();
sp.first.put();
}

/**
* Стоп выполнения досуга поедания рациона
*/
public void stop() {
running = false;
}
}

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

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

public final class QueueNode {
final long hunger;
final long placeInQ;
final CountDownLatch latch = new CountDownLatch(1);
volatile boolean isReleased;

QueueNode(long eaten, AtomicLong qOrder) {
this.hunger = eaten;
this.placeInQ = qOrder.getAndIncrement();
}
}
38 changes: 38 additions & 0 deletions src/main/java/org/labs/RationShelf.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.labs;

import java.util.concurrent.atomic.AtomicLong;

/**
* Шкаф со всеми рационами
*/
public class RationShelf {
private final AtomicLong rationLeft;

public RationShelf(long rationLeft) {
this.rationLeft = new AtomicLong(rationLeft);
}

/**
* Метод для взятия рациона, если ещё есть остатки
* @return Исход попытки взятия рациона
*/
public boolean takeRation() {
while (true) {
long curCount = rationLeft.get();
if (curCount <= 0) {
return false;
}
if (rationLeft.compareAndSet(curCount, curCount - 1)) {
return true;
}
}
}

/**
* Получить число оставшихся рационов
* @return Число оставшихся рационов
*/
public AtomicLong getLeftRations() {
return rationLeft;
}
}
Loading