Лабораторная работа №1
Описание
Программа вычисляет сумму произведений строк квадратной матрицы с использованием трех подходов:
- Однопоточная реализация
- Многопоточная реализация с использованием ThreadPool
- Реализация с использованием ForkJoinPool
Цель — сравнить эффективность различных способов многопоточной обработки данных и замерить время выполнения для каждого подхода.
Как запустить лабораторную работу
-
Соберите проект и получите
.jarфайл:mvn packageЛибо перейдите сразу к шагу 2, если
.jarуже собран. -
Соберите Docker-образ:
docker build -t app -f .dockerfile . -
Запустите контейнер:
docker run -i app
Используемые технологии
- Java
- Maven — для сборки проекта
- Docker — для упаковки и запуска приложения в контейнере
- Параллельное программирование:
ExecutorService(ThreadPool)ForkJoinPool(пул задач с рекурсивным делением данных)
Что делает программа
- Заполняет матрицу случайными числами от 1 до 100.
- Вычисляет сумму произведений элементов в каждой строке матрицы тремя способами:
- Однопоточная реализация — классический линейный перебор строк.
- ThreadPool — распределение строк по пулу потоков.
- ForkJoinPool — разбиение задачи на подзадачи с использованием
RecursiveTask.
- Для каждого способа выводит время выполнения в наносекундах.
public static void calculateSumOfRow() { // Фнукция подсчета в одном потоке
LocalDateTime startTime = LocalDateTime.now();
long s = System.nanoTime();
int sum = 0;
for (int[] ints : matrix) {
int product = 1;
for (int anInt : ints) {
product *= anInt;
}
sum += product;
}
long st = System.nanoTime() - s;
System.out.println("Время выполнения в одном потоке: \t" + st);
}
public static void calculateSumOfRowWithThreadPool() { // Фнукция подсчета с помощью ThreadPool
int sum = 0;
int numThreads = Runtime.getRuntime().availableProcessors(); // Определение количества потоков на основании процессора
ExecutorService executor = Executors.newFixedThreadPool(numThreads);
List<Future<Integer>> futures = new ArrayList<>();
long s = System.nanoTime();
for (int i = 0; i < matrix.length; i++) { // Создание задач выполнения
final int row = i;
futures.add(executor.submit(() -> {
int product = 1;
for (int j = 0; j < matrix[row].length; j++) {
product *= matrix[row][j];
}
return product;
}));
}
for (Future<Integer> future : futures) { // Подсчет всех значений
try {
sum += future.get();
} catch (InterruptedException | ExecutionException e) {
System.out.println(e.getMessage());
}
}
executor.shutdown();
long st = System.nanoTime() - s;
System.out.println("Время выполнения с ThreadPool: \t\t" + st);
}
public static void calculateSumOfRowWithForkPool(){ // ПОдсчет через ForkPool
PartTask pt = new PartTask(matrix, 0, size + 1); // Создание сущности парта
ForkJoinPool forkJoinPool = new ForkJoinPool();
long s = System.nanoTime();
forkJoinPool.invoke(pt);
long st = System.nanoTime() - s;
System.out.println("Время выполнения с ForkJoinPool: \t" + st);
}
}
class PartTask extends RecursiveTask<Integer> { // Класс задачи
private final int[][] matrix;
private final int start, stop;
public PartTask(int[][] matrix, int start, int stop) {
this.matrix = matrix;
this.start = start;
this.stop = stop;
}
@Override
protected Integer compute() { // Вычисление части парта(Использование рекурсий)
if (start + 1 == stop) {
return Arrays.stream(matrix[start]).sum();
}
PartTask p1 = new PartTask(matrix, start, (start + stop) / 2);
PartTask p2 = new PartTask(matrix, (start + stop) / 2, stop);
p1.fork();
p2.fork();
return p1.join() + p2.join();
}
}
Особенности кода
- Используется замер времени выполнения с помощью
System.nanoTime(). - ThreadPool автоматически подстраивается под количество доступных ядер CPU.
- Реализация с ForkJoinPool демонстрирует работу паттерна divide-and-conquer (разделяй и властвуй).
- Для ForkJoinPool реализован собственный класс
PartTask, унаследованный отRecursiveTask<Integer>.
Тесты (Примеры ввода и вывода)
Тест 1:
Ввод:
Введите размер матрицы (от 2 до 1000): 10
Вывод:
Время выполнения в одном потоке: 2800
Время выполнения с ThreadPool: 1793455
Время выполнения с ForkJoinPool: 599719
Тест 2:
Ввод:
Введите размер матрицы (от 2 до 1000): 100
Вывод:
Время выполнения в одном потоке: 66801
Время выполнения с ThreadPool: 5646255
Время выполнения с ForkJoinPool: 568506
Тест 3:
Ввод:
Введите размер матрицы (от 2 до 1000): 1000
Вывод:
Время выполнения в одном потоке: 2352923
Время выполнения с ThreadPool: 14560843
Время выполнения с ForkJoinPool: 917609
Вывод
В рамках лабораторной работы были реализованы и протестированы три подхода к параллельной обработке данных. ForkJoinPool показал лучшее соотношение скорости и масштабируемости при увеличении размера матрицы, особенно по сравнению с классическим ThreadPool.
Были закреплены навыки работы с потоками, параллельными вычислениями и измерением производительности различных подходов.