📒 Readme ver 1.0 25.11.2025 22:30
Лабораторна робота №8 Робота з потоками
📝 Завдання
Багатопоточна перевірка простих чисел
- Розробити багатопоточну програму для перевірки простих чисел
- Кожен потік обробляє частину масиву незалежно
- Результати зберігаються у текстовий файл
- Реалізувати синхронізацію доступу до спільних ресурсів
- Створити модульні тести 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
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
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
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
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
🛠️ Реалізація коду
Конфігурація 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>
<?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;
}
}
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());
}
}
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);
}
}
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));
}
}
}
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));
}
}
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"));
}
}
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 | Фреймворк для тестування |