Приклади розв’язку задач на потоки виконання (Threads). Робота з файлами в потоках. Сортування в потоках
Зміст
- 1. Задача. Паралельні цілочисельні масиви в потоках
- 2. Задача. Запис масивів чисел у файли в різних потоках. Реалізація інтерфейсу Runnable
- 3. Задача. Читання з файлу масивів чисел
- 4. Задача. Потік рядків типу String. Сортування масиву рядків у декількох потоках різними алгоритмами
- Зв’язані теми
Пошук на інших ресурсах:
1. Задача. Паралельні цілочисельні масиви в потоках
Умова задачі. Користувач вводить з клавіатури значення в масив. Після чого запускаються два потоки. Перший потік знаходить максимум в масиві, другий – мінімум. Результати обчислень повертаються у метод main().
Розв’язок. Дана задача розв’язується з оголошенням одного класу, в якому реалізовано одночасно пошук мінімального та максимального елементу масиву. За бажанням, можна реалізувати два окремі класи, що будуть визначати відповідні потоки.
// Клас, що реалізує дочірній потік у даній задачі class ThreadMinMax implements Runnable { private Thread thr; // посилання на дочірній потік private int[] AI; private int maximum; // максимальне значення private int minimum; // мінімальне значення // Конструктор - отримує масив цілих чисел public ThreadMinMax(int[] _AI) { // Ініціалізація масиву AI = _AI; // Створити потік thr = new Thread(this, "Thread1."); // Запустити потік виконання thr.start(); } // Метод, в якому вписується код роботи потоку. // У нашому випадку вписується код пошуку мінімального значення та // заповнення змінної maximum. public void run() { int max = AI[0]; int min = AI[0]; for (int i=1; i<AI.length; i++) { if (max<AI[i]) max = AI[i]; if (min>AI[i]) min = AI[i]; } maximum = max; minimum = min; } // Методи доступу до полів класу public Thread getThread() { return thr; } public int getMax() { return maximum; } public int getMin() { return minimum; } } public class TrainThreads2 { public static void main(String[] args) { // 1. Оголосити тестувальний масив int[] AI = { 2, 3, 4, 8, -1 }; // 2. Створити два дочірні потоки, отримати посилання на них ThreadMinMax t1 = new ThreadMinMax(AI); ThreadMinMax t2 = new ThreadMinMax(AI); // 3. Зчитати результат try { // Очікування завершення потоків t1, t2 - обов'язково, // інакше можна отримати нульові значення t1.getThread().join(); t2.getThread().join(); } catch (InterruptedException e) { System.out.println("Error."); } // Зчитати результат після завершення потоків t1, t2 System.out.println("max = " + t1.getMax()); System.out.println("min = " + t1.getMin()); } }
Результат роботи програми
max = 8 min = -1
⇑
2. Задача. Запис масивів чисел у файли в різних потоках. Реалізація інтерфейсу Runnable
Умова задачі. Задано три цілочисельних масиви. Записати ці масиви у файл в паралельних потоках. Створити клас SaveAsThread для представлення потоку, що записує цілочисельний масив у файл.
Розв’язок. Інкапсуляція потоку реалізована в класі SaveAsThread. Клас містить такі складові:
- AI – масив, який потрібно записати у файл в потоці;
- filename – ім’я файлу, в який записується масив AI;
- threadName – ім’я поточного потоку;
- t – посилання на клас Thread, яке реалізує поточний потік;
- конструктор SaveAsThread(), який отримує вхідними параметрами масив, ім’я файлу та ім’я поточного потоку;
- метод start(), який запускає потік;
- метод run(), в якому виконується код потоку. Цей метод запускається при виклику методу start() класу Thread за внутрішнім посиланням t.
import java.util.*; import java.io.*; // Клас, що предствляє потік запису масиву цілих чисел у файл class SaveAsThread implements Runnable { private int[] AI; // масив, що записується private String filename; // внутрішнє поле - ім'я файлу private String threadName; // Ім'я потоку private Thread t; // посилання на поточний потік // Конструктор - отримує 3 параметри: // - AI - масив, що потрібно записати у файл; // - filename - ім'я файлу, в який записується масив AI; // - threadName - ім'я потоку. public SaveAsThread(int[] AI, String filename, String threadName) { // Запам'ятати посилання на масив this.AI = AI; // Запам'ятати ім'я файлу this.filename = filename; // Запам'ятати ім'я потоку this.threadName = threadName; // Створити потік з іменем "SaveThread" t = new Thread(this, "SaveThread"); } // метод, що запускає поточний потік public void start() { t.start(); // викликати метод run() } // У методі run() вказується код запису у файл public void run() { // Сповістити про початок виконання потоку System.out.println("Begin thread: " + threadName); try { // Створити зв'язок з текстовим файлом filename: fOut -> filename FileOutputStream fOut = new FileOutputStream(filename); // Створити зв'язок з екземпляром fOut: ps -> fOut -> filename PrintStream pS = new PrintStream(fOut); // Записати масив AI у файл pS.println(AI.length); for (int i=0; i<AI.length; i++) { pS.println(AI[i]); } // Закрити файлові потоки pS.close(); fOut.close(); } catch (IOException e) { // Вивести повідомлення про помилку System.out.println("Error: " + e.getMessage()); } // Сповістити про завершення виконання потоку System.out.println("End thread: " + threadName); } } public class Threads { public static void main(String[] args) throws IOException { // Запис масивів у різні файли в різних потоках // 1. Створити три цілочисельних масиви int[] AI1 = { 2, 4, 3, 8, 9, 11, 7 }; int[] AI2 = { 1, 8, 7, 6, 3 }; int AI3[] = { 7, 7, 9, 9, 4, 2 }; // 2. Створити три потоки SaveAsThread t1 = new SaveAsThread(AI1, "AI1.txt", "t1"); SaveAsThread t2 = new SaveAsThread(AI2, "AI2.txt", "t2"); SaveAsThread t3 = new SaveAsThread(AI3, "AI3.txt", "t3"); // 3. Запустити потоки на виконання t1.start(); t2.start(); t3.start(); } }
Результат виконання програми
Begin thread: t1 End thread: t1 Begin thread: t2 Begin thread: t3 End thread: t2 End thread: t3
Після запуску програми буде створено три файли з іменами “AI1.txt”, “AI2.txt”, “AI3.txt”, які містять значення елементів відповідних масивів.
⇑
3. Задача. Читання з файлу масивів чисел
Дана задача є зворотною по відношенню до попередньої задачі.
Умова задачі. Задано три файли з іменами “AI1.txt”, “AI2.txt”, “AI3.txt”. У кожному з файлів записані цілочисельні масиви згідно з наступним форматом:
N number1 number2 ... numberN
тут
- N – кількість чисел, які збережені у файлі;
- number1, number2, …, numberN – безпосередньо числа.
Розв’язок. Для розв’язку задачі оголошується спеціальний клас ReadFileAsThread, який містить наступні складові:
- AI – масив, який читається з файлу;
- filename – ім’я файлу, з якого потрібно зчитати дані у масив AI;
- thr – посилання на клас Thread. Дане посилання вказує на поточний потік;
- конструктор, який вхідними параметрами отримує ім’я файлу та ім’я потоку;
- метод Start() – призначений для запуску потоку з коду клієнта. Даний метод викликає метод start() класу Thread за посиланням thr. У результаті викликається метод run();
- метод get() – метод доступу до масиву AI з зовнішнього коду клієнта (функції main()).
В іншому класі Threads реалізована функція main(), в якій продемонстровано роботу класу ReadFileAsThread. Створюється 3 потоки. Читаються масиви. Зчитані масиви виводяться на екран.
Виконання програми передбачає наявність файлів “AI1.txt”, “AI2.txt”, “AI3.txt”.
import java.util.*; import java.io.*; // Клас, що інкапсулює потік читання масиву чисел з файлу. // Клас розширює можливості класу Thread class ReadFileAsThread extends Thread { private int[] AI; // масив, який потрібно прочитати з файлу private String filename; // ім'я файлу private Thread thr; // посилання на поточний потік // Конструктор public ReadFileAsThread(String filename, String threadName) { // Запам'яатати ім'я файлу this.filename = filename; // Створити потік з іменем threadName thr = new Thread(this, threadName); } // Запуск потоку public void Start() { thr.start(); // Запускається метод run() } // Безпосередній код виконання потоку public void run() { // 1. Сповістити про початок виконання потоку - читання з файлу System.out.println("Begin thread: " + thr.getName()); FileInputStream fInput; try { // 2. Зв'язати файл filename з екземпляром fInput: fInput <- filename fInput = new FileInputStream(filename); // 3. Зв'язати екземпляр класу Scanner з екземпляром fInput: // scanner <- fInput <- filename Scanner scanner = new Scanner(fInput); // 4. Оголосити додаткові змінні int count; // Кількість чисел у масиві // 5. Зчитати кількість чисел у масиві count = scanner.nextInt(); // 6. Виділити пам'ять для масиву AI AI = new int[count]; // 7. Прочитати числа for (int i=0; i<AI.length; i++) AI[i] = scanner.nextInt(); // 8. Закрити потоки scanner.close(); fInput.close(); } catch (IOException e) { // Вивести повідомлення про помилку System.out.println("Error: " + e.getMessage()); } // Сповістити про закінчення виконання потоку - читання з файлу System.out.println("End thread: " + thr.getName()); } // Метод доступу до масиву AI public int[] get() { return AI; } } public class Threads { public static void main(String[] args) throws IOException { // Запис масивів у різні файли в різних потоках // 1. Створити три посилання на цілочисельні масиви int[] AI1 = null; int[] AI2 = null; int AI3[] = null; // 2. Створити три потоки ReadFileAsThread t1 = new ReadFileAsThread("AI1.txt", "t1"); ReadFileAsThread t2 = new ReadFileAsThread("AI2.txt", "t2"); ReadFileAsThread t3 = new ReadFileAsThread("AI3.txt", "t3"); // 3. Запустити потоки на виконання t1.start(); t2.start(); t3.start(); // 4. Дочекатись завершення потоків щоб отримати коректний результат. // Для цього використовується метод join() try { t1.join(); t2.join(); t3.join(); } catch (InterruptedException e) { // Якщо помилка, то вивести повідомлення System.out.println("Error: " + e.getMessage()); return; } // 4. Зчитати масиви AI1 = t1.get(); AI2 = t2.get(); AI3 = t3.get(); // 5. Вивести масиви для контролю System.out.print("AI1 = [ "); for (int d : AI1) System.out.print(d + " "); System.out.println(" ]"); System.out.print("AI2 = [ "); for (int d : AI2) System.out.print(d + " "); System.out.println(" ]"); System.out.print("AI3 = [ "); for (int d : AI3) System.out.print(d + " "); System.out.println(" ]"); } }
Результат виконання програми
Begin thread: t2 Begin thread: t1 Begin thread: t3 End thread: t3 End thread: t1 End thread: t2 AI1 = [ 2 4 3 8 9 11 7 ] AI2 = [ 1 8 7 6 3 ] AI3 = [ 7 7 9 9 4 2 ]
⇑
4. Задача. Потік рядків типу String. Сортування масиву рядків у декількох потоках різними алгоритмами
Умова задачі. Реалізувати наступні алгоритми сортування:
- сортування вставками;
- сортування вибором;
- сортування бульбашкою.
Продемонструвати сортування на масивах рядків типу String[]. Кожен вид сортування повинен запускатись в окремому потоці.
Розв’язок. Для реалізації алгоритмів сортування, у програмі реалізовано три класи:
- SelectionSortThread – інкапсулює потік, в якому здійснюється сортування методом вибору;
- InsertionSortThread – інкапсулює потік, в якому здійснюється сортування методом вставки;
- BubbleSortThread – інкапсулює потік, в якому здійснюється сортування методом бульбашки.
Усі класи мають схожі внутрішні змінні та методи:
- AS – масив, що сортується;
- t – посилання на поточний потік типу Thread;
- конструктор;
- метод Start(), який запускає потік на виконання;
- метод run(), який містить код виконання потоку;
- метод get() для доступу до внутрішнього масиву AS;
- метод getThread() для доступу до потоку t типу Thread.
// Клас, який інкапсулює потік, що сортує рядки // методом вибору в спадному порядку. class SelectionSortThread extends Thread { // Внутрішні поля класу private String[] AS; // масив рядків private Thread t; // посилання на поточний потік // Конструктор, отримує параметри: // - масив рядків ззовні; // - ім'я поточного потоку. public SelectionSortThread(String[] AS, String threadName) { // Зберегти посилання на зовнішній масив this.AS = AS; // Створити потік t = new Thread(this, threadName); } // Метод, що запускає потік public void Start() { t.start(); // виклик методу класу Thread } // Метод, що виконується в потоці public void run() { // Посортувати рядки методом сортування вибором // 1. Сповістити про початок сортування System.out.println("Begin => " + t.getName()); // 2. Оголосити додаткові внутрішні змінні int i, j, k; String s; // 3. Цикл сортування вибором в порядку спадання рядків for (i=0; i<AS.length; i++) { // i - поточний крок k = i; // Пошук найбільшого (максимального) елементу s = AS[i]; for (j=i+1; j<AS.length; j++) if (AS[j].compareTo(s)>0) { k = j; // індекс максимального елементу s = AS[j]; } // Обміняти місцями максимальний елемент з AS[i] AS[k] = AS[i]; AS[i] = s; } // 4. Сповістити про закінчення потоку System.out.println("End => " + t.getName()); } // Метод доступу до масиву AS public String[] get() { return AS; } // Метод доступу до потоку t public Thread getThread() { return t; } // Метод, що виводить масив AS на екран public void Print() { System.out.print(t.getName() + " = [ "); for (String s : AS) System.out.print(s + " "); System.out.println(" ]"); } } // Клас, що інкапсулює потік, в якому сортується масив // рядків методом вставок. Потік створюється шляхом успадкування класу Thread. // Сортування відбувається у порядку спадання елементів. class InsertionSortThread extends Thread { // Внутрішні поля класу private String[] AS; // масив рядків private Thread t; // поточний потік public InsertionSortThread(String[] AS, String threadName) { // Зберегти посилання на зовнішній масив this.AS = AS; // Створити потік t = new Thread(this, threadName); } // Метод, що запускає потік з клієнтського коду public void Start() { t.start(); // неявний виклик методу run() } // Метод, що запускає потік з клієнтського коду public void run() { // Тут потрібно реалізувати сортування методом вставки // 1. Сповістити про початок сортування System.out.println("Begin => " + t.getName()); // 2. Оголосити внутрішні змінні int i, j; String s; // 3. Цикл сортування вставками for (i=0; i<AS.length; i++) { // i - номер проходу s = AS[i]; // Пошук місця елементу в послідовності for (j=i-1; j>=0 && AS[j].compareTo(s)<0; j--) { // зсувати елемент вправо наскільки можливо AS[j+1] = AS[j]; } AS[j+1] = s; } // 4. Сповістити про закінчення сортування System.out.println("End => " + t.getName()); } // Метод доступу до масиву AS public String[] get() { return AS; } // Метод доступ до потоку t public Thread getThread() { return t; } // Метод, що виводить масив AS на екран public void Print() { System.out.print(t.getName() + " = [ "); for (String s : AS) System.out.print(s + " "); System.out.println(" ]"); } } // Клас, що інкапсулює потік, в якому відбувається сортування бульбашкою. // Сортування рядків відбувається у спадному порядку. // Клас реалізує інтерфейс Runnable class BubbleSortThread extends Thread { // Внутрішні змінні класу private String[] AS; // масив рядків private Thread t; // посилання на поточний потік // Конструктор, отримує 2 параметри: // - масив рядків; // - ім'я поточного потоку. public BubbleSortThread(String[] AS, String threadName) { // Зберегти зовнішній масив this.AS = AS; // Створити новий потік t = new Thread(this, threadName); } // Метод, що запускає потік на виконання public void Start() { t.start(); } // Метод, в якому виконується потік public void run() { // Сортування методом бульбашки // 1. Сповістити про початок сортування System.out.println("Begin => " + t.getName()); // 2. Оголосити внутрішні змінні int i, j; String s; // 3. Цикл сортування for (i=0; i<AS.length; i++) { // i - номер проходу for (j=AS.length-1; j>i; j--) { // внутрішній цикл проходу // сортування за спаданням if (AS[j-1].compareTo(AS[j])<0) { s = AS[j]; AS[j] = AS[j-1]; AS[j-1] = s; } } } // 4. Сповістити про кінець сортування System.out.println("End => " + t.getName()); } // Метод доступу до масиву AS public String[] get() { return AS; } // Метод доступ до потоку t public Thread getThread() { return t; } // Метод, що виводить масив AS на екран public void Print() { System.out.print(t.getName() + " = [ "); for (String s : AS) System.out.print(s + " "); System.out.println(" ]"); } } // Клас, що реалізує функцію main(), в якій тестується // робота потоків public class Threads { public static void main(String[] args) { // Демонстрація сортування масивів рядків у різних потоках // 1. Оголосити три масиви рядків. Масиви мають однакові значення String[] AS1 = { "abc", "def", "bcd", "dsd", "aff", "jkl", "ffg"}; String[] AS2 = { "abc", "def", "bcd", "dsd", "aff", "jkl", "ffg"}; String[] AS3 = { "abc", "def", "bcd", "dsd", "aff", "jkl", "ffg"}; // 2. Оголосити екземпляри класів-потоків SelectionSortThread t1 = new SelectionSortThread(AS1, "t1:SelectionSort"); InsertionSortThread t2 = new InsertionSortThread(AS2, "t2:InsertionSort"); BubbleSortThread t3 = new BubbleSortThread(AS3, "t3:BubbleSort"); // 3. Запустити потоки t1, t2, t3 паралельно t1.Start(); t2.Start(); t3.Start(); // 4. Дочекатись завершення потоків t1, t2, t3, щоб отримати коректний результат try { t1.getThread().join(); t2.getThread().join(); t3.getThread().join(); } catch (InterruptedException e) { // Якщо помилка, то вивести повідомлення System.out.println("Error: " + e.getMessage()); return; } // 5. Вивести посортовані масиви на екран t1.Print(); t2.Print(); t3.Print(); } }
Після запуску на виконання програма видала наступний результат
Begin => t2:InsertionSort Begin => t3:BubbleSort Begin => t1:SelectionSort End => t3:BubbleSort End => t2:InsertionSort End => t1:SelectionSort t1:SelectionSort = [ jkl ffg dsd def bcd aff abc ] t2:InsertionSort = [ jkl ffg dsd def bcd aff abc ] t3:BubbleSort = [ jkl ffg dsd def bcd aff abc ]
⇑
Зв’язані теми
- Багатозадачність. Потоки виконання. Основні поняття
- Засоби Java для роботи з потоками виконання. Клас Thread. Інтерфейс Runnable. Головний потік. Створення дочірнього потоку
- Методи класу Thread: getName(), run(), start(), sleep(). Приклади
- Методи класу Thread: join(), isAlive(), getPriority(), setPriority(). Приклади
⇑