Compare commits
7 Commits
7868add638
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| c6f7893725 | |||
| 5365327d2f | |||
| 548619a0e5 | |||
| eb3fa2fbad | |||
| 9e5b33d600 | |||
| f26810f2f8 | |||
| 0f2f9532ef |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
target
|
||||
target
|
||||
tmp
|
||||
97
lab3/report/report.txt
Normal file
97
lab3/report/report.txt
Normal file
@@ -0,0 +1,97 @@
|
||||
h1. Отчёт по лабораторной работе №3 (Тищенко Артём)
|
||||
|
||||
|
||||
h2. Задание
|
||||
|
||||
В данной работе необходимо написать многопоточное приложение, которое эмулирует заданную модель. Студент сам должен спроектировать потоки, которые отвечают за поведение сущностей из полученного задания. Взаимодействие потоков должно быть синхронизировано и приложение должно быть протестирование на наличие dead locks и race conditions. Приложение не должно переставать работать из-за изменения задержек и модель не должна быть полностью синхронной.
|
||||
|
||||
h2. Мой вариант
|
||||
|
||||
Обогреватель — вентилятор. Каждая комната в здании имеет управляющий терминал для наблюдения и контролирования за окружающей средой. Каждый терминал измеряет и выводит текущую температуру и влажность. В каждой комнаты кроме того установлена предпочтительная температура и влажность (пара чисел). Если текущие значения температуры или влажности вышли за пределы предпочтительных настроек более чем на 1%, тогда станция включает/выключает обогреватель или вентилятор. Должны быть следующие процессы: сенсоры, настройки, обогреватель — вентилятор, контроллер.
|
||||
|
||||
h2. Архитектура и компоненты реализации
|
||||
|
||||
Приложение состоит из нескольких основных классов:
|
||||
|
||||
# *App*
|
||||
#* Точка входа в программу.
|
||||
#* Создаёт объект _Room_, объекты _Settings_ и _Controller_, затем запускает поток с контроллером.
|
||||
#* При необходимости может выступать стартовой площадкой для других потоков (например, если вы решите запускать класс _Room_ как отдельный поток).
|
||||
# *Room* (реализует _Runnable_)
|
||||
#* Симулирует физические процессы в комнате.
|
||||
#* Хранит внутренние поля:
|
||||
#** _temperature_ – текущая температура в градусах Цельсия.
|
||||
#** _humidity_ – текущая относительная влажность (0..1).
|
||||
#* Периодически изменяет температуру и влажность случайным образом.
|
||||
#* Методы _adjustTemperature()_ и _adjustHumidity()_ помечены как _synchronized_, чтобы избежать гонок данных.
|
||||
#* При запуске в потоке (метод _run()_) постоянно вносит небольшие случайные изменения в температуру и влажность.
|
||||
# *Sensor* (реализует _Runnable_)
|
||||
#* Представляет набор датчиков в комнате.
|
||||
#* Периодически (с заданным интервалом) считывает актуальную _temperature_ и _humidity_ из _Room_, добавляя некоторую случайную погрешность.
|
||||
#* Хранит результат измерений во внутренних полях (например, _temperature_, _humidity_).
|
||||
#* Эти значения затем читает _Controller_.
|
||||
# *Heater* (реализует _Runnable_)
|
||||
#* Нагреватель комнаты.
|
||||
#* При включённом состоянии (_isOn_ = true) повышает температуру в комнате на случайную величину.
|
||||
#* Работает в собственном потоке, периодически внося изменения в _Room_ (через _adjustTemperature()_).
|
||||
# *Fan* (реализует _Runnable_)
|
||||
#* Вентилятор, уменьшающий влажность.
|
||||
#* При включённом состоянии (_isOn_ = true) понижает влажность в комнате на случайную величину.
|
||||
#* Работает в собственном потоке, периодически внося изменения в _Room_ (через _adjustHumidity()_).
|
||||
# *Controller* (реализует _Runnable_)
|
||||
#* Логический контроллер, который:
|
||||
#** Читает измерения с _Sensor_ (температура и влажность).
|
||||
#** Сравнивает их с желаемыми параметрами из _Settings_.
|
||||
#** Включает/выключает _Heater_ и _Fan_, если показатели выходят за пределы допустимых значений.
|
||||
#* Запускается в отдельном потоке и периодически (по заданному интервалу) проверяет состояние сенсоров и управляет нагревателем/вентилятором.
|
||||
#* При завершении (прерывании) аккуратно останавливает все подчинённые потоки (_Sensor_, _Heater_, _Fan_).
|
||||
# *Settings* (реализует _Runnable_)
|
||||
#* Хранит желаемую температуру и влажность, заданные пользователем.
|
||||
#* Периодически (в методе _run()_) может изменять эти настройки, симулируя поведение человека, корректирующего параметры.
|
||||
#* Хранится и используется в _Controller_ для решения, когда включать/выключать нагреватель и вентилятор.
|
||||
# *Utils*
|
||||
#* Вспомогательный класс с методами:
|
||||
#** _sleep(...)_ – безопасная пауза (учитывает _InterruptedException_).
|
||||
#** _sleepRandomTime(...)_ – пауза случайной длины (диапазон задаётся параметрами).
|
||||
#* Используется всеми потоками для имитации задержек, дабы избежать полного синхронного шага у всех объектов.
|
||||
|
||||
|
||||
h2. Тестирование
|
||||
|
||||
Ниже приведён краткий обзор тестовых классов и методов, покрывающих основные аспекты работы приложения:
|
||||
|
||||
# *AppTest*
|
||||
#* *testNoDeadlock()* – проверяет отсутствие взаимной блокировки (deadlock), когда одновременно запущены _Controller_ и _Room_.
|
||||
#* *testNoRaceCondition()* – тест на отсутствие гонок (race conditions), в ходе которого периодически проверяются значения температуры и влажности в _Room_.
|
||||
# *FanTests*
|
||||
#* *testFanOn()* – проверяет, что при включённом вентиляторе влажность в комнате уменьшается, а температура остаётся без изменений.
|
||||
#* *testFanOff()* – проверяет, что при выключенном вентиляторе ни температура, ни влажность не меняются.
|
||||
# *HeaterTests*
|
||||
#* *testHeaterOn()* – убеждается, что при включённом нагревателе температура в комнате увеличивается, а влажность не меняется.
|
||||
#* *testHeaterOff()* – при выключенном нагревателе показатели комнаты не меняются.
|
||||
# *RoomTests*
|
||||
#* *testTemperatureAndHumidityChange()* – запускает _Room_ в отдельном потоке и проверяет, что температура и влажность действительно меняются со временем.
|
||||
# *SensorTests*
|
||||
#* *testSensor()* – эмулирует работу сенсора с помощью макета комнаты (_MockRoom_), сверяет реальное значение температуры и влажности с тем, что возвращает сенсор, учитывая допустимую погрешность.
|
||||
|
||||
h3. Результаты тестов
|
||||
|
||||
Ниже представлена иллюстрация (вывод в консоли) после успешного запуска _mvn test_, где видно, что все тесты проходят:
|
||||
|
||||
!tests-results.png!
|
||||
|
||||
h2. Результаты работы программы
|
||||
|
||||
Результаты 60 секунд симуляции представлены на скриншоте ниже.
|
||||
|
||||
<pre>
|
||||
mvn package
|
||||
java -jar .\target\lab3-1.0-SNAPSHOT.jar
|
||||
</pre>
|
||||
|
||||
!results.png!
|
||||
|
||||
|
||||
h2. Исходный код
|
||||
|
||||
*Исходный код доступен на "GitHub":https://github.com/Arity-T/java_labs/tree/main/lab3 .*
|
||||
@@ -7,9 +7,14 @@ public class App {
|
||||
Locale.setDefault(Locale.ENGLISH);
|
||||
|
||||
Room room = new Room();
|
||||
Settings settings = new Settings(28, 0.4);
|
||||
Thread roomThread = new Thread(room);
|
||||
Settings settings = new Settings(room, 28, 0.4);
|
||||
Thread settingsThread = new Thread(settings);
|
||||
Controller controller = new Controller(room, settings);
|
||||
Thread controllerThread = new Thread(controller);
|
||||
|
||||
roomThread.start();
|
||||
settingsThread.start();
|
||||
controllerThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ public class Fan implements Runnable {
|
||||
}
|
||||
|
||||
private Random random = new Random();
|
||||
private double humidityMaxStep = 0.03;
|
||||
private long maxStepTimeMs = 3000;
|
||||
private double humidityMaxStep = 0.005;
|
||||
private long maxStepTimeMs = 500;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
@@ -28,8 +28,8 @@ public class Heater implements Runnable {
|
||||
}
|
||||
|
||||
private Random random = new Random();
|
||||
private double temperatureMaxStep = 1;
|
||||
private long maxStepTimeMs = 3000;
|
||||
private double temperatureMaxStep = 0.25;
|
||||
private long maxStepTimeMs = 500;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
@@ -39,16 +39,18 @@ public class Room implements Runnable {
|
||||
|
||||
// Параметры произвольного изменения температуры и влажности в комнате
|
||||
private Random random = new Random();
|
||||
private double temperatureMaxStep = 1;
|
||||
private double humidityMaxStep = 0.05;
|
||||
private long maxStepTimeMs = 3000;
|
||||
private double temperatureMaxStep = 0.12; // примерно 0.5% от средних значений
|
||||
private double humidityMaxStep = 0.003;
|
||||
private long maxStepTimeMs = 500;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Пусть температура и влажность произвольно изменяются со временем
|
||||
// Пусть температура и влажность почти произвольно изменяются со временем,
|
||||
// но со временем становится немного холоднее (комната остывает), а влажность
|
||||
// немного растёт (потому что нужно иногда проветривать).
|
||||
while (!Thread.interrupted()) {
|
||||
temperature += (random.nextDouble() - 0.5) * 2 * temperatureMaxStep;
|
||||
humidity += (random.nextDouble() - 0.5) * 2 * humidityMaxStep;
|
||||
temperature += (random.nextDouble() - 0.6) * 2 * temperatureMaxStep;
|
||||
humidity += (random.nextDouble() - 0.4) * 2 * humidityMaxStep;
|
||||
|
||||
Utils.sleepRandomTime((long) (maxStepTimeMs * 0.5), maxStepTimeMs);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import java.util.Random;
|
||||
* Симулирует переодическое изменение настроек пользователем.
|
||||
*/
|
||||
public class Settings implements Runnable {
|
||||
private Room room;
|
||||
|
||||
private double temperature;
|
||||
|
||||
public double getTemperature() {
|
||||
@@ -18,24 +20,33 @@ public class Settings implements Runnable {
|
||||
return humidity;
|
||||
}
|
||||
|
||||
public Settings(double temperature, double humidity) {
|
||||
public Settings(Room room, double temperature, double humidity) {
|
||||
this.room = room;
|
||||
this.temperature = temperature;
|
||||
this.humidity = humidity;
|
||||
}
|
||||
|
||||
// Параметры произвольного изменения настроек температуры и влажности в комнате
|
||||
private Random random = new Random();
|
||||
private double temperatureMaxStep = 10;
|
||||
private double humidityMaxStep = 0.15;
|
||||
private long maxStepTimeMs = 20000;
|
||||
private double temperatureMaxStep = 6;
|
||||
private double humidityMaxStep = 0.10;
|
||||
private long maxStepTimeMs = 30000;
|
||||
|
||||
private void log(String string) {
|
||||
System.out.printf("[Settings in room %s] %s\n", room.name, string);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (!Thread.interrupted()) {
|
||||
Utils.sleepRandomTime((long) (maxStepTimeMs * 0.5), maxStepTimeMs);
|
||||
|
||||
temperature += (random.nextDouble() - 0.5) * 2 * temperatureMaxStep;
|
||||
humidity += (random.nextDouble() - 0.5) * 2 * humidityMaxStep;
|
||||
|
||||
Utils.sleepRandomTime((long) (maxStepTimeMs * 0.5), maxStepTimeMs);
|
||||
log(String.format(
|
||||
"Changed to temperature %.2fC°, humidity %.2f%%",
|
||||
temperature, humidity));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,76 @@ package ru.spbstu.telematics.java;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class SampleTests {
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.ThreadMXBean;
|
||||
|
||||
public class AppTest {
|
||||
|
||||
/**
|
||||
* Тест проверяет, что при параллельном запуске всех потоков
|
||||
* не возникает взаимной блокировки (deadlock).
|
||||
*/
|
||||
@Test
|
||||
void sampleTest() {
|
||||
assertTrue(true);
|
||||
void testNoDeadlock() throws InterruptedException {
|
||||
Room room = new Room();
|
||||
Settings settings = new Settings(room, 28.0, 0.4);
|
||||
Controller controller = new Controller(room, settings);
|
||||
Thread controllerThread = new Thread(controller);
|
||||
Thread roomThread = new Thread(room);
|
||||
|
||||
controllerThread.start();
|
||||
roomThread.start();
|
||||
|
||||
// Будем проверять наличие deadlock несколько раз с периодом 1 сек
|
||||
// Общая длительность теста 10 секунд
|
||||
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
Thread.sleep(1000);
|
||||
|
||||
long[] threadIds = threadBean.findDeadlockedThreads();
|
||||
// Если возвращается не null, значит обнаружен deadlock
|
||||
assertNull(threadIds, "Обнаружен deadlock.");
|
||||
}
|
||||
|
||||
controllerThread.interrupt();
|
||||
roomThread.interrupt();
|
||||
|
||||
controllerThread.join();
|
||||
roomThread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Тест проверяет отсутствие гонок (race conditions),
|
||||
* когда значения становятся очевидно некорректными или программа "падает".
|
||||
*/
|
||||
@Test
|
||||
void testNoRaceCondition() throws InterruptedException {
|
||||
Room room = new Room();
|
||||
Settings settings = new Settings(room, 25.0, 0.4);
|
||||
Controller controller = new Controller(room, settings);
|
||||
Thread controllerThread = new Thread(controller, "Controller-Thread");
|
||||
Thread roomThread = new Thread(room, "Room-Thread");
|
||||
|
||||
controllerThread.start();
|
||||
roomThread.start();
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
long testDuration = 10000;
|
||||
|
||||
while (System.currentTimeMillis() - startTime < testDuration) {
|
||||
double t = room.getTemperature();
|
||||
double h = room.getHumidity();
|
||||
|
||||
assertTrue(t > -50 && t < 100, "Температура вышла за пределы допустимых значений, возможна гонка");
|
||||
assertTrue(h >= -0.5 && h <= 1.5, "Влажность вышла за пределы допустимых значений, возможна гонка");
|
||||
|
||||
Thread.sleep(200);
|
||||
}
|
||||
|
||||
controllerThread.interrupt();
|
||||
roomThread.interrupt();
|
||||
|
||||
controllerThread.join();
|
||||
roomThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user