# Лабораторная работа №1. Вариант 12.
**Задание: определить среднее арифметическое элементов матрицы ниже главной
диагонали.**

## Как запустить лабораторную работу?
Чтобы её запустить нам потребуется:
* Сомпилировать файл с кодом в формате java с помощью комманды: javac Main.java
* Запустить программу с помощью команды: java Main

## Какие технологии использовали?
Используемые технологии:
*   java.io.*: Для операций ввода-вывода, таких как чтение и запись файлов.
*   java.util.*: Содержит основные классы коллекций (List, ArrayList, Scanner и т.д.) и другие утилиты.
*   java.util.concurrent.*: Предоставляет инструменты для многопоточного программирования, включая ExecutorService, ThreadPoolExecutor, Future, Callable, ForkJoinPool и другие.
ThreadPoolExecutor: Управляет пулом потоков фиксированного размера для параллельного выполнения задач, отправляемых в очередь. 
ForkJoinPool: Реализует "кражу работы" (work-stealing) для эффективного параллельного выполнения рекурсивно разделяемых задач.

## Что программа делает?
Данная программа вычисляет среднее арифметическое элементов ниже главной диагонали квадратной матрицы, сравнивая однопоточный и два многопоточных подхода: ThreadPoolExecutor и ForkJoinPool.
Пользователь вводит размер матрицы, она генерируется, сохраняется в файл, считывается обратно, а затем для каждого алгоритма измеряется время выполнения и выводится результат. 
Программа обрабатывает некорректный ввод пользователя и завершает работу с ошибкой. 
**Цель: продемонстрировать преимущества многопоточности в решении данной вычислительной задачи
и сравнить производительность различных способов распараллеливания.**

## Примеры входных и выходных значений.

Введите размер матрицы (квадратной): 1000\
Однопоточный алгоритм: 4 мс, Среднее арифметическое: 99.49616216216216\
Многопоточный алгоритм с использованием ThreadPoolExecutor: 58 мс, Среднее арифметическое: 99.49616216216216\
Многопоточный алгоритм с использованием ForkJoinPool: 9 мс, Среднее арифметическое: 99.49616216216216\

Введите размер матрицы (квадратной): 5000\
Однопоточный алгоритм: 90 мс, Среднее арифметическое: 99.5001852370474\
Многопоточный алгоритм с использованием ThreadPoolExecutor: 202 мс, Среднее арифметическое: 99.5001852370474\
Многопоточный алгоритм с использованием ForkJoinPool: 68 мс, Среднее арифметическое: 99.5001852370474\

Введите размер матрицы (квадратной): 9000\
Однопоточный алгоритм: 312 мс, Среднее арифметическое: 99.48965727056093\
Многопоточный алгоритм с использованием ThreadPoolExecutor: 378 мс, Среднее арифметическое: 99.48965727056093\
Многопоточный алгоритм с использованием ForkJoinPool: 113 мс, Среднее арифметическое: 99.48965727056093\

Введите размер матрицы (квадратной): 12000\
Однопоточный алгоритм: 650 мс, Среднее арифметическое: 99.4960964941523\
Многопоточный алгоритм с использованием ThreadPoolExecutor: 607 мс, Среднее арифметическое: 99.4960964941523\
Многопоточный алгоритм с использованием ForkJoinPool: 207 мс, Среднее арифметическое: 99.4960964941523\

Введите размер матрицы (квадратной): 14000\
Однопоточный алгоритм: 8901 мс, Среднее арифметическое: 99.50303716592002\
Многопоточный алгоритм с использованием ThreadPoolExecutor: 1280 мс, Среднее арифметическое: 99.50303716592002\
Многопоточный алгоритм с использованием ForkJoinPool: 142 мс, Среднее арифметическое: 99.50303716592002\

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