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

## Задание

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

**Необходимо:**

1. Разработать однопоточный вариант алгоритма и замерить время его работы.
2. Разработать параллельный вариант алгоритма с использованием `ThreadPoolExecutor` и замерить время его работы.
3. Разработать параллельный вариант алгоритма с использованием `ForkJoinPool` и замерить время его работы.

**Вариант:** "Определить минимальный элемент матрицы ниже главной диагонали"

---

## Подготовка

### Генерация матрицы

```java
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. Однопоточный вариант

```java
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

```java
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)` создаём пул фиксированного размера.  
- Рассчитываем размер блока строк:  
  ```java
  int blockSize = (n + numThreads - 1) / numThreads;
  ```  
  Таким образом, матрица делится на равные (или почти равные) части.  
- Для каждого блока определяется диапазон строк от `startRow` до `endRow`. В лямбда-выражении происходит последовательный поиск минимума в этом диапазоне (начиная с `Math.max(startRow, 1)`, чтобы не обрабатывать строку 0).  
- Результаты задач собираются в список `futures`, после чего перебираются и вычисляется глобальный минимум.  
- После получения результатов пул потоков закрывается через `executor.shutdown()`.

---

## Пункт 3. Параллельный вариант с использованием ForkJoinPool

```java
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 можно командой:

```bash
java -jar Lab-1-jar-with-dependencies.jar
```