## Лабораторная работа №1.


Разработка многопоточного приложения с использованием Java Concurrency
согласно варианту задания.
Необходимо:
1. Разработать однопоточный вариант алгоритма и замерить время его работы.
2. Разработать параллельный вариант алгоритма с использованием
ThreadPoolExecutor и замерить время его работы
3. Разработать параллельный вариант алгоритма с использованием ForkJoinPoll
и замерить время его работы.
ВАЖНО: Массив генерируется до работы всех вариантов алгоритмов. Все три
алгоритма обрабатывают три одинаковых массива.

**Вариант 13**
Определить сумму из произведений элементов каждой строки матрицы.

**Запуск программы**
javac MatrixSumOfProducts.java
java MatrixSumOfProducts

**Используемые технологии**
*java.util.concurrent — библиотека для разработки многопоточных программ.
*BigInteger — для работы с большими числами, чтобы избежать переполнения при умножении.

**Что делает программа**
1. Генерирует матрицу заданного размера.

2. Вычисляет сумму произведений элементов каждой строки матрицы:

2.1 Однопоточным алгоритмом.

2.2 Параллельным алгоритмом с использованием ThreadPoolExecutor.

2.3 Параллельным алгоритмом с использованием ForkJoinPool.

3. Замеряет время выполнения каждого из алгоритмов.

**Тесты**
```
Размерность матрицы 100x100

Single-threaded time: 17 ms
ThreadPoolExecutor time: 73 ms
ForkJoinPool time: 36 ms
```

```
Размерность матрицы 1000x1000

Single-threaded time: 1639 ms
ThreadPoolExecutor time: 1440 ms
ForkJoinPool time: 464 ms
```

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

**Наблюдения:**
1. **Однопоточный алгоритм** работает быстрее на небольших матрицах (например, 100x100), так как отсутствуют накладные расходы на создание и синхронизацию потоков.
2. **ThreadPoolExecutor** показывает хорошие результаты на больших матрицах (например, 1000x1000), но его производительность может быть ниже, чем у `ForkJoinPool`, из-за менее эффективного распределения задач между потоками.
3. **ForkJoinPool** демонстрирует наилучшую производительность на больших задачах, так как он оптимизирован для рекурсивного разделения задач и эффективного использования ресурсов процессора.