Java. Приклади розв’язку задач на потоки виконання

Приклади розв’язку задач на потоки виконання (Threads). Робота з файлами в потоках. Сортування в потоках


Зміст


Пошук на інших ресурсах:




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 ]

 


 Зв’язані теми