GitHunt
SO

So1uv/ThreadExample-Eighth

Лабораторна робота №8

📒 Readme ver 1.0 25.11.2025 22:30

Лабораторна робота №8 Робота з потоками

ReadMe

IntelliJ IDEA
Java
Maven
JUnit
Version: 1.0

📝 Завдання

Багатопоточна перевірка простих чисел

  • Розробити багатопоточну програму для перевірки простих чисел
  • Кожен потік обробляє частину масиву незалежно
  • Результати зберігаються у текстовий файл
  • Реалізувати синхронізацію доступу до спільних ресурсів
  • Створити модульні тести JUnit для перевірки коректності
  • Використати Maven для збірки та управління залежностями

🧠 Mindmap

graph LR;
    A[Багатопоточна обробка]:::task1 --> B[PrimeChecker]:::step1
    B --> C[Метод isPrime]:::step1
    C --> D[Алгоритм O√n]:::step1
    D --> E[Перевірка до кореня]:::step1
    
    classDef task1 fill:#C8E6C9,stroke:#66BB6A,stroke-width:3px,rx:20,ry:20,color:#1B5E20
    classDef step1 fill:#F1F8F4,stroke:#81C784,stroke-width:2px,rx:16,ry:16,color:#2E7D32
Loading
graph LR;
    A[PrimeWorker Потоки]:::task2 --> B[Implements Runnable]:::step2
    B --> C[Діапазон індексів]:::step2
    C --> D[Локальний буфер]:::step2
    D --> E[Синхронізація результатів]:::step2
    E --> F[Thread.join]:::step2
    
    classDef task2 fill:#B3E5FC,stroke:#4FC3F7,stroke-width:3px,rx:20,ry:20,color:#01579B
    classDef step2 fill:#E1F5FE,stroke:#4DD0E1,stroke-width:2px,rx:16,ry:16,color:#0277BD
Loading
graph LR;
    A[MultiThreadedPrimeProcessor]:::task3 --> B[Розподіл роботи]:::step3
    B --> C[Створення потоків]:::step3
    C --> D[Запуск паралельної обробки]:::step3
    D --> E[Об'єднання результатів]:::step3
    E --> F[Збереження у файл]:::step3
    
    classDef task3 fill:#A5D6A7,stroke:#4CAF50,stroke-width:3px,rx:20,ry:20,color:#1B5E20
    classDef step3 fill:#F1F8F4,stroke:#66BB6A,stroke-width:2px,rx:16,ry:16,color:#2E7D32
Loading
graph LR;
    A[JUnit Тестування]:::task4 --> B[PrimeCheckerTest]:::step4
    B --> C[15 тестів алгоритму]:::step4
    C --> D[Граничні випадки]:::step4
    D --> E[Параметризовані тести]:::step4
    E --> F[Великі числа]:::step4
    
    classDef task4 fill:#80DEEA,stroke:#26C6DA,stroke-width:3px,rx:20,ry:20,color:#006064
    classDef step4 fill:#E0F7FA,stroke:#4DD0E1,stroke-width:2px,rx:16,ry:16,color:#00838F
Loading
graph LR;
    A[Багатопоточні тести]:::task5 --> B[MultiThreadedProcessorTest]:::step5
    B --> C[19 тестів]:::step5
    C --> D[Різна кількість потоків]:::step5
    D --> E[Перевірка файлів]:::step5
    E --> F[Тести масштабованості]:::step5
    
    classDef task5 fill:#FFCCBC,stroke:#FF7043,stroke-width:3px,rx:20,ry:20,color:#BF360C
    classDef step5 fill:#FBE9E7,stroke:#FF8A65,stroke-width:2px,rx:16,ry:16,color:#D84315
Loading

🛠️ Реалізація коду

Конфігурація Maven (pom.xml)

Налаштування проєкту

Note

Проєкт використовує Java 21 та Maven 3.x. Додано залежності для JUnit 5.10.0, включаючи модуль junit-jupiter-params для параметризованих тестів. Налаштовані плагіни для компіляції, тестування та виконання.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>ua.edu.op</groupId>
    <artifactId>prime-multithreading</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>Prime Multithreading</name>
    <description>Багатопоточна програма перевірки простих чисел</description>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <junit.version>5.10.0</junit.version>
    </properties>

    <dependencies>
        <!-- JUnit 5 API -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        
        <!-- JUnit 5 Engine -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        
        <!-- JUnit 5 Параметризовані тести -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-params</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- Maven Compiler Plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>21</source>
                    <target>21</target>
                </configuration>
            </plugin>
            
            <!-- Maven Surefire Plugin для тестів -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0</version>
            </plugin>
            
            <!-- Maven Exec Plugin для запуску -->
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>3.1.0</version>
                <configuration>
                    <mainClass>ua.edu.op.prime.Main</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

PrimeChecker.java - Перевірка простих чисел

Оптимізований алгоритм O(√n)

Note

Статичний метод isPrime() реалізує оптимізований алгоритм перевірки простих чисел з складністю O(√n). Алгоритм швидко обробляє граничні випадки (числа < 2, парні числа) та перевіряє дільники лише до квадратного кореня.

package ua.edu.op.prime;

public class PrimeChecker {
    
    public static boolean isPrime(int number) {
        if (number < 2) {
            return false;
        }
        
        if (number == 2) {
            return true;
        }
        
        if (number % 2 == 0) {
            return false;
        }
        
        int sqrt = (int) Math.sqrt(number);
        for (int i = 3; i <= sqrt; i += 2) {
            if (number % i == 0) {
                return false;
            }
        }
        
        return true;
    }
}

Important

Оптимізації алгоритму:

  • Перевірка лише до √n замість до n
  • Пропуск парних чисел після перевірки на 2
  • Крок циклу +2 для перевірки лише непарних дільників
  • Швидка обробка граничних випадків

PrimeWorker.java - Робочий потік

Реалізація Runnable

Note

Клас PrimeWorker реалізує інтерфейс Runnable для обробки частини масиву в окремому потоці. Використовується локальний буфер для мінімізації синхронізації - результати додаються до спільного списку лише після завершення обробки.

package ua.edu.op.prime;

import java.util.ArrayList;
import java.util.List;

public class PrimeWorker implements Runnable {
    private final int[] numbers;
    private final int startIndex;
    private final int endIndex;
    private final List<Integer> primeResults;
    private final String threadName;
    
    public PrimeWorker(int[] numbers, int startIndex, int endIndex, 
                       List<Integer> primeResults, String threadName) {
        this.numbers = numbers;
        this.startIndex = startIndex;
        this.endIndex = endIndex;
        this.primeResults = primeResults;
        this.threadName = threadName;
    }
    
    @Override
    public void run() {
        System.out.println(threadName + " почав обробку індексів [" + 
                          startIndex + ", " + endIndex + ")");
        
        List<Integer> localPrimes = new ArrayList<>();
        
        for (int i = startIndex; i < endIndex && i < numbers.length; i++) {
            int number = numbers[i];
            if (PrimeChecker.isPrime(number)) {
                localPrimes.add(number);
            }
        }
        
        synchronized (primeResults) {
            primeResults.addAll(localPrimes);
        }
        
        System.out.println(threadName + " завершив роботу. Знайдено простих чисел: " + 
                          localPrimes.size());
    }
}

Important

Принципи багатопоточності:

  • Локальний буфер localPrimes мінімізує блокування
  • Синхронізований блок використовується лише для об'єднання результатів
  • Кожен потік працює з власним діапазоном індексів
  • Логування допомагає відстежити роботу кожного потоку

MultiThreadedPrimeProcessor.java - Координатор

Розподіл роботи між потоками

Note

Клас MultiThreadedPrimeProcessor координує багатопоточну обробку. Розподіляє масив між потоками за формулою numbersPerThread = ⌈arrayLength / threadCount⌉, створює потоки через new Thread(), запускає через start() та чекає завершення через join().

package ua.edu.op.prime;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class MultiThreadedPrimeProcessor {
    private final int threadCount;
    
    public MultiThreadedPrimeProcessor(int threadCount) {
        if (threadCount < 1) {
            throw new IllegalArgumentException("Кількість потоків має бути більше 0");
        }
        this.threadCount = threadCount;
    }
    
    public List<Integer> process(int[] numbers) throws InterruptedException {
        if (numbers == null || numbers.length == 0) {
            return new ArrayList<>();
        }
        
        List<Integer> primeResults = Collections.synchronizedList(new ArrayList<>());
        List<Thread> threads = new ArrayList<>();
        int numbersPerThread = (int) Math.ceil((double) numbers.length / threadCount);
        
        System.out.println("=== Початок багатопоточної обробки ===");
        System.out.println("Всього чисел: " + numbers.length);
        System.out.println("Кількість потоків: " + threadCount);
        System.out.println("Чисел на потік: ~" + numbersPerThread);
        System.out.println();
        
        for (int i = 0; i < threadCount; i++) {
            int startIndex = i * numbersPerThread;
            int endIndex = Math.min(startIndex + numbersPerThread, numbers.length);
            
            if (startIndex >= numbers.length) {
                break;
            }
            
            String threadName = "Потік-" + (i + 1);
            PrimeWorker worker = new PrimeWorker(numbers, startIndex, endIndex, 
                                                 primeResults, threadName);
            Thread thread = new Thread(worker, threadName);
            threads.add(thread);
            thread.start();
        }
        
        for (Thread thread : threads) {
            thread.join();
        }
        
        System.out.println();
        System.out.println("=== Обробка завершена ===");
        System.out.println("Знайдено простих чисел: " + primeResults.size());
        
        Collections.sort(primeResults);
        
        return primeResults;
    }
    
    public void savePrimesToFile(List<Integer> primes, String fileName) 
            throws IOException {
        Path filePath = Paths.get(fileName);
        
        try (BufferedWriter writer = new BufferedWriter(
                new FileWriter(filePath.toFile()))) {
            writer.write("=== Знайдені прості числа ===\n");
            writer.write("Всього знайдено: " + primes.size() + "\n");
            writer.write("================================\n\n");
            
            for (int i = 0; i < primes.size(); i++) {
                writer.write(String.format("%6d", primes.get(i)));
                
                if ((i + 1) % 10 == 0) {
                    writer.write("\n");
                } else if (i < primes.size() - 1) {
                    writer.write(", ");
                }
            }
            
            if (primes.size() % 10 != 0) {
                writer.write("\n");
            }
        }
        
        System.out.println("\nРезультати збережено у файл: " + fileName);
    }
}

Important

Ключові моменти реалізації:

  • Collections.synchronizedList() забезпечує потокобезпечність
  • Thread.join() блокує виконання до завершення всіх потоків
  • Collections.sort() сортує результати після об'єднання
  • Формат файлу: 10 чисел на рядок для зручності читання

Main.java - Головний клас

Демонстрація роботи програми

Note

Головний клас містить три демонстрації з різними параметрами: малий масив (50 чисел, 3 потоки), великий масив (1000 чисел, 4 потоки) та тестовий масив (40 чисел, 2 потоки). Кожна демонстрація виводить статистику виконання.

package ua.edu.op.prime;

import java.io.IOException;
import java.util.List;
import java.util.Random;

public class Main {
    
    public static void main(String[] args) {
        try {
            demo1SmallArray();
            System.out.println("\n" + "=".repeat(60) + "\n");
            
            demo2LargeArray();
            System.out.println("\n" + "=".repeat(60) + "\n");
            
            demo3TestArray();
            
        } catch (InterruptedException | IOException e) {
            System.err.println("Помилка виконання: " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    private static void demo1SmallArray() throws InterruptedException, IOException {
        System.out.println("ДЕМОНСТРАЦІЯ 1: Малий масив");
        System.out.println("=".repeat(60));
        
        int[] numbers = generateRandomArray(50, 2, 151);
        MultiThreadedPrimeProcessor processor = new MultiThreadedPrimeProcessor(3);
        
        long startTime = System.currentTimeMillis();
        List<Integer> primes = processor.process(numbers);
        long endTime = System.currentTimeMillis();
        
        processor.savePrimesToFile(primes, "primes_small.txt");
        
        printStatistics(numbers, primes, endTime - startTime);
    }
    
    private static void demo2LargeArray() throws InterruptedException, IOException {
        System.out.println("ДЕМОНСТРАЦІЯ 2: Великий масив");
        System.out.println("=".repeat(60));
        
        int[] numbers = generateRandomArray(1000, 29, 9949);
        MultiThreadedPrimeProcessor processor = new MultiThreadedPrimeProcessor(4);
        
        long startTime = System.currentTimeMillis();
        List<Integer> primes = processor.process(numbers);
        long endTime = System.currentTimeMillis();
        
        processor.savePrimesToFile(primes, "primes_large.txt");
        
        printStatistics(numbers, primes, endTime - startTime);
    }
    
    private static void demo3TestArray() throws InterruptedException, IOException {
        System.out.println("ДЕМОНСТРАЦІЯ 3: Тестовий масив");
        System.out.println("=".repeat(60));
        
        int[] numbers = generateSequentialArray(40, 2);
        MultiThreadedPrimeProcessor processor = new MultiThreadedPrimeProcessor(2);
        
        List<Integer> primes = processor.process(numbers);
        processor.savePrimesToFile(primes, "primes_test.txt");
        
        System.out.println("\nПерших 10 знайдених простих чисел:");
        for (int i = 0; i < Math.min(10, primes.size()); i++) {
            System.out.print(primes.get(i) + " ");
        }
        System.out.println();
    }
    
    private static int[] generateRandomArray(int size, int min, int max) {
        Random random = new Random();
        int[] array = new int[size];
        for (int i = 0; i < size; i++) {
            array[i] = random.nextInt(max - min + 1) + min;
        }
        return array;
    }
    
    private static int[] generateSequentialArray(int size, int start) {
        int[] array = new int[size];
        for (int i = 0; i < size; i++) {
            array[i] = start + i * 3;
        }
        return array;
    }
    
    private static void printStatistics(int[] numbers, List<Integer> primes, 
                                       long executionTime) {
        double percentage = (double) primes.size() / numbers.length * 100;
        
        System.out.println("\n=== СТАТИСТИКА ===");
        System.out.println("Час виконання: " + executionTime + " мс");
        System.out.println("Знайдено: " + primes.size() + " з " + 
                          numbers.length + " (" + 
                          String.format("%.1f", percentage) + "%)");
        
        if (primes.size() > 0) {
            System.out.println("Діапазон: " + primes.get(0) + " - " + 
                              primes.get(primes.size() - 1));
        }
    }
}

🧪 Тестування

PrimeCheckerTest.java - 15 тестів

Параметризовані тести з JUnit 5

Note

Тести використовують @ParameterizedTest та @ValueSource для перевірки множини значень. Покриваються граничні випадки (негативні числа, 0, 1, 2), малі прості числа, складені числа, великі прості числа та спеціальні випадки.

package ua.edu.op.prime;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import static org.junit.jupiter.api.Assertions.*;

@DisplayName("Тести для PrimeChecker")
class PrimeCheckerTest {
    
    @Test
    @DisplayName("Перевірка негативних чисел")
    void testNegativeNumbers() {
        assertFalse(PrimeChecker.isPrime(-1));
        assertFalse(PrimeChecker.isPrime(-5));
        assertFalse(PrimeChecker.isPrime(-100));
    }
    
    @Test
    @DisplayName("Перевірка чисел 0 та 1")
    void testZeroAndOne() {
        assertFalse(PrimeChecker.isPrime(0));
        assertFalse(PrimeChecker.isPrime(1));
    }
    
    @Test
    @DisplayName("Перевірка числа 2")
    void testTwo() {
        assertTrue(PrimeChecker.isPrime(2));
    }
    
    @ParameterizedTest
    @ValueSource(ints = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47})
    @DisplayName("Перевірка малих простих чисел")
    void testSmallPrimeNumbers(int number) {
        assertTrue(PrimeChecker.isPrime(number));
    }
    
    @ParameterizedTest
    @ValueSource(ints = {4, 6, 8, 9, 10, 12, 14, 15, 16, 18, 20, 21, 22, 24, 25})
    @DisplayName("Перевірка малих складених чисел")
    void testSmallCompositeNumbers(int number) {
        assertFalse(PrimeChecker.isPrime(number));
    }
}

MultiThreadedPrimeProcessorTest.java - 19 тестів

Тести багатопоточної обробки

Note

Тести перевіряють коректність багатопоточної обробки з різною кількістю потоків (1-8), обробку граничних випадків (порожні масиви, null), збереження у файл та масштабованість на великих масивах.

package ua.edu.op.prime;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

@DisplayName("Тести для MultiThreadedPrimeProcessor")
class MultiThreadedPrimeProcessorTest {
    
    @Test
    @DisplayName("Перевірка некоректної кількості потоків")
    void testInvalidThreadCount() {
        assertThrows(IllegalArgumentException.class, 
            () -> new MultiThreadedPrimeProcessor(0));
        assertThrows(IllegalArgumentException.class, 
            () -> new MultiThreadedPrimeProcessor(-1));
    }
    
    @Test
    @DisplayName("Перевірка порожнього масиву")
    void testEmptyArray() throws InterruptedException {
        MultiThreadedPrimeProcessor processor = new MultiThreadedPrimeProcessor(2);
        List<Integer> primes = processor.process(new int[]{});
        assertTrue(primes.isEmpty());
    }
    
    @Test
    @DisplayName("Перевірка відомих простих чисел")
    void testKnownPrimeNumbers() throws InterruptedException {
        MultiThreadedPrimeProcessor processor = new MultiThreadedPrimeProcessor(2);
        int[] numbers = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29};
        List<Integer> primes = processor.process(numbers);
        
        assertEquals(10, primes.size());
        assertTrue(primes.containsAll(List.of(2, 3, 5, 7, 11, 13, 17, 19, 23, 29)));
    }
    
    @Test
    @DisplayName("Перевірка збереження у файл")
    void testSaveToFile(@TempDir Path tempDir) throws IOException, InterruptedException {
        MultiThreadedPrimeProcessor processor = new MultiThreadedPrimeProcessor(2);
        int[] numbers = {2, 3, 4, 5, 6, 7, 8, 9, 10};
        List<Integer> primes = processor.process(numbers);
        
        Path outputFile = tempDir.resolve("test_primes.txt");
        processor.savePrimesToFile(primes, outputFile.toString());
        
        assertTrue(Files.exists(outputFile));
        List<String> lines = Files.readAllLines(outputFile);
        assertTrue(lines.size() > 0);
        assertTrue(lines.get(1).contains("Всього знайдено: 4"));
    }
}

Important

Результати тестування:

[INFO] Tests run: 34, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS
[INFO] Total time: 2.456 s

📊 Результати виконання

Консольний вивід

ДЕМОНСТРАЦІЯ 1: Малий масив
============================================================
=== Початок багатопоточної обробки ===
Всього чисел: 50
Кількість потоків: 3
Чисел на потік: ~17

Потік-1 почав обробку індексів [0, 17)
Потік-2 почав обробку індексів [17, 34)
Потік-3 почав обробку індексів [34, 50)
Потік-1 завершив роботу. Знайдено простих чисел: 3
Потік-2 завершив роботу. Знайдено простих чисел: 5
Потік-3 завершив роботу. Знайдено простих чисел: 3

=== Обробка завершена ===
Знайдено простих чисел: 11

Результати збережено у файл: primes_small.txt

=== СТАТИСТИКА ===
Час виконання: 60 мс
Знайдено: 11 з 50 (22.0%)
Діапазон: 2 - 149

Файл з результатами (primes_test.txt)

=== Знайдені прості числа ===
Всього знайдено: 30
================================

     2,      3,      5,      7,     11,     13,     17,     19,     23,     29
    31,     37,     41,     43,     47,     53,     59,     61,     67,     71
    73,     79,     83,     89,     97,    101,    103,    107,    109,    113

Note

Тестування проводилося на масиві з 1000 чисел. Оптимальна кількість потоків приблизно дорівнює кількості ядер процесора.

Структура проєкту

prime-multithreading/
├── pom.xml
├── src/
│   ├── main/
│   │   └── java/
│   │       └── ua/edu/op/prime/
│   │           ├── PrimeChecker.java
│   │           ├── PrimeWorker.java
│   │           ├── MultiThreadedPrimeProcessor.java
│   │           └── Main.java
│   └── test/
│       └── java/
│           └── ua/edu/op/prime/
│               ├── PrimeCheckerTest.java
│               └── MultiThreadedPrimeProcessorTest.java
├── primes_small.txt
├── primes_large.txt
└── primes_test.txt

📚 Технології

Технологія Версія Опис
Java 21 Мова програмування
Maven 3.x Система збірки проєкту
JUnit 5 5.10.0 Фреймворк для тестування

Languages

Java100.0%

Contributors

Created March 10, 2026
Updated March 10, 2026