1 Лабораторная работа
Задание
Разработка многопоточного приложения с использованием Java Concurrency согласно варианту задания.
Необходимо:
- Разработать однопоточный вариант алгоритма и замерить время его работы.
- Разработать параллельный вариант алгоритма с использованием
ThreadPoolExecutorи замерить время его работы. - Разработать параллельный вариант алгоритма с использованием
ForkJoinPoolи замерить время его работы.
Вариант: "Определить минимальный элемент матрицы ниже главной диагонали"
Подготовка
Генерация матрицы
private static int[][] generateMatrix(int n) {
int[][] matrix = new int[n][n];
Random random = new Random();
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
matrix[i][j] = random.nextInt(20001) - 10000;
}
}
return matrix;
}
Описание:
Метод получает размер матрицы n и создаёт массив размером n x n, заполняя его числами от -10000 до 10000 (так как random.nextInt(20001) возвращает значения от 0 до 20000, а затем вычитается 10000).
Пункт 1. Однопоточный вариант
public static int findMinSingleThread(int[][] matrix) {
int n = matrix.length;
int min = Integer.MAX_VALUE;
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
if (matrix[i][j] < min) {
min = matrix[i][j];
}
}
}
return min;
}
Описание:
Создаётся переменная min, инициализированная максимальным значением типа int. Далее происходит последовательный проход по элементам матрицы ниже главной диагонали (для строки i перебираются столбцы с 0 до i-1), и если найден элемент меньше текущего минимума, переменная min обновляется.
Пункт 2. Параллельный вариант с использованием ThreadPoolExecutor
public static int findMinThreadPool(int[][] matrix) {
int n = matrix.length;
int numThreads = Runtime.getRuntime().availableProcessors();
ExecutorService executor = Executors.newFixedThreadPool(numThreads);
List<Future<Integer>> futures = new ArrayList<>();
int blockSize = (n + numThreads - 1) / numThreads;
for (int t = 0; t < numThreads; t++) {
int startRow = t * blockSize;
int endRow = Math.min(n, startRow + blockSize);
Future<Integer> future = executor.submit(() -> {
int localMin = Integer.MAX_VALUE;
for (int i = Math.max(startRow, 1); i < endRow; i++) {
for (int j = 0; j < i; j++) {
if (matrix[i][j] < localMin) {
localMin = matrix[i][j];
}
}
}
return localMin;
});
futures.add(future);
}
int min = Integer.MAX_VALUE;
try {
for (Future<Integer> future : futures) {
int result = future.get();
if (result < min) {
min = result;
}
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
executor.shutdown();
return min;
}
Описание:
- Получаем число доступных процессоров и сохраняем его в переменную
numThreads. - С помощью
Executors.newFixedThreadPool(numThreads)создаём пул фиксированного размера. - Рассчитываем размер блока строк:
Таким образом, матрица делится на равные (или почти равные) части.
int blockSize = (n + numThreads - 1) / numThreads; - Для каждого блока определяется диапазон строк от
startRowдоendRow. В лямбда-выражении происходит последовательный поиск минимума в этом диапазоне (начиная сMath.max(startRow, 1), чтобы не обрабатывать строку 0). - Результаты задач собираются в список
futures, после чего перебираются и вычисляется глобальный минимум. - После получения результатов пул потоков закрывается через
executor.shutdown().
Пункт 3. Параллельный вариант с использованием ForkJoinPool
public static int findMinForkJoin(int[][] matrix) {
ForkJoinPool forkJoinPool = new ForkJoinPool();
MatrixMinTask task = new MatrixMinTask(matrix, 1, matrix.length);
int min = forkJoinPool.invoke(task);
forkJoinPool.shutdown();
return min;
}
static class MatrixMinTask extends RecursiveTask<Integer> {
private static final int THRESHOLD = 100;
private int[][] matrix;
private int startRow;
private int endRow;
public MatrixMinTask(int[][] matrix, int startRow, int endRow) {
this.matrix = matrix;
this.startRow = startRow;
this.endRow = endRow;
}
@Override
protected Integer compute() {
if (endRow - startRow <= THRESHOLD) {
int localMin = Integer.MAX_VALUE;
for (int i = startRow; i < endRow; i++) {
for (int j = 0; j < i; j++) {
if (matrix[i][j] < localMin) {
localMin = matrix[i][j];
}
}
}
return localMin;
} else {
int mid = (startRow + endRow) / 2;
MatrixMinTask leftTask = new MatrixMinTask(matrix, startRow, mid);
MatrixMinTask rightTask = new MatrixMinTask(matrix, mid, endRow);
leftTask.fork();
int rightResult = rightTask.compute();
int leftResult = leftTask.join();
return Math.min(leftResult, rightResult);
}
}
}
Описание:
- Создаётся экземпляр
ForkJoinPoolдля распараллеливания рекурсивных задач. - Запускается задача
MatrixMinTask, которая ищет минимум в строках сstartRow = 1доmatrix.length. - В классе
MatrixMinTaskопределён порогTHRESHOLD = 100. Если диапазон строк меньше или равен порогу, задача решается последовательно. В противном случае диапазон делится на две части:- Левая задача: обрабатывает строки от
startRowдоmid. - Правая задача: обрабатывает строки от
midдоendRow.
- Левая задача: обрабатывает строки от
- Для асинхронного выполнения используется метод
fork()(для левой задачи), а затем с помощьюcompute()иjoin()происходит объединение результатов двух подзадач.
Запуск
Запустить jar можно командой:
java -jar Lab-1-jar-with-dependencies.jar