# Лабораторная №1
Разработка многопоточного приложения с использованием Java Concurrency
согласно варианту задания.

Необходимо:
1) Разработать однопоточный вариант алгоритма и замерить время его работы.
2) Разработать параллельный вариант алгоритма с использованием
ThreadPoolExecutor и замерить время его работы
3) Разработать параллельный вариант алгоритма с использованием ForkJoinPoll
и замерить время его работы.

Массив генерируется до работы всех вариантов алгоритмов. Все три
алгоритма обрабатывают три одинаковых массива.

## Вариант и задание
3) Разделить элементы матрицы на среднее арифметическое всех ее элементов.

### Описание
Данная программа разработана для сравнения производительности однопоточного и многопоточного подходов к делению элементов матрицы на среднее арифметическое её элементов. Задача состоит из двух основных частей:

1. Вычисление среднего арифметического
2. Деление элементов матрицы на среднее арифметическое

Эти этапы выполняются с разными вариантами многопоточности, чтобы выявить их влияние на производительность.

### Как запустить
```sh
javac DividingByAverage.java
java DividingByAverage
```

### Используемые технологии
Программа использует Java Concurrency, включая классы и интерфейсы:
1) `ExecutorService`
2) `ThreadPoolExecutor`
3) `ForkJoinPool`
4) `RecursiveAction`

### Что делает программа
1. Генерирует матрицу заданного размера, заполненную случайными значениями.
2. Вычисляет среднее арифметическое элементов матрицы.
3. Делит все элементы матрицы на среднее арифметическое.
4. Замеряет время выполнения для однопоточного и двух многопоточных методов.

## Результаты тестов

### Для матрцы 5000*5000:

```
Однопоточный алгоритм:
array[100][100] = 15.0
Среднее арифметическое: 50.00305776
array[100][100] = 0.29998165456191894
Время выполнения: 59 мс

Алгоритм с ThreadPoolExecutor:
Среднее арифметическое: 50.00305776
array[100][100] = 0.29998165456191894
Время выполнения: 56 мс

Алгоритм с ForkJoinPool:
Среднее арифметическое: 50.00305776
array[100][100] = 0.29998165456191894
Время выполнения: 66 мс
```

### Для матрцы 10000*10000:

```
Однопоточный алгоритм:
array[100][100] = 38.0
Среднее арифметическое: 50.00220663
array[100][100] = 0.7599664607041763
Время выполнения: 186 мс

Алгоритм с ThreadPoolExecutor:
Среднее арифметическое: 50.00220663
array[100][100] = 0.7599664607041763
Время выполнения: 140 мс

Алгоритм с ForkJoinPool:
Среднее арифметическое: 50.00220663
array[100][100] = 0.7599664607041763
Время выполнения: 174 мс
```

### Для матрцы 15000*15000:

```
Однопоточный алгоритм:
array[100][100] = 4.0
Среднее арифметическое: 49.99820225777778
array[100][100] = 0.08000287649097934
Время выполнения: 388 мс

Алгоритм с ThreadPoolExecutor:
Среднее арифметическое: 49.99820225777778
array[100][100] = 0.08000287649097934
Время выполнения: 323 мс

Алгоритм с ForkJoinPool:
Среднее арифметическое: 49.99820225777778
array[100][100] = 0.08000287649097934
Время выполнения: 379 мс
```

## Вывод ##
Пока матрицы небольших размеров, можно заметить, что многопоточные алгоритмы могут не иметь преимущества по времени выполнения перед однопоточным алгоритмом или даже быть хуже.
Это происходит из-за того, что ThreadPool и ForkJoin затрачивают дополнительные ресурсы на создание и поддержание потоков.

По мере роста размеров матрицы, многопоточные алгоритмы показывают большую эффективность, чем однопоточный. Однопоточный алгоритм начинает работать медленнее из-за большого количества элементов. 
ThreadPool и ForkJoin начинают работать быстрее, так как дополнительные траты на поддержание потоков с лихвой компенсируются эффективностью многопоточного метода решения задачи.