Java. Потоки виконання. Методи класу Thread

Потоки виконання. Методи класу Thread: join(), isAlive(), getPriority(), setPriority()

Перед вивченням даної теми рекомендується ознайомитись з наступними темами:


Зміст


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




1. Чому потрібно, щоб основний (викликаючий) потік завершувався першим? Пояснення. Метод join(). Необхідність застосування, загальна форма

Нехай задано два потоки:

  • основний (викликаючий). З основного потоку запускається (викликається) дочірній потік;
  • дочірній (викликаний).

Для того, щоб з дочірнього потоку отримати результат (об’єкт, дані), основний (викликаючий) потік повинен завершуватись останнім. Якщо основний потік завершується швидше ніж дочірній потік, то, скоріш за все, будуть отримуватись нульові (початкові) або проміжні значення з дочірнього потоку, що є помилкою.

Для того, щоб основний потік завершувався останнім у мові Java використовується метод join(). Загальна форма методу наступна:

public final void join() throws InterruptedException

тут

  • InterruptedException – клас виключення, яке виникає коли який-небудь інший потік перервав поточний потік. Після виникнення цього виключення перерваний статус поточного потоку скидується.

 

2. Метод join(). Приклад

Задача. Реалізувати виклик дочірнього потоку з основного потоку. У дочірньому потоці визначається максимальне значення масиву цілих чисел. Отриманий результат (максимальне значення) повинен повертатись в основний потік і виводитись на екран.

Розв’язок. Для створення дочірнього потоку розробляється окремий клас. У нашому випадку клас має ім’я Thread1. У цьому класі оголошуються всі необхідні елементи:

  • посилання thr на дочірній потік типу Thread;
  • масив AI цілих чисел;
  • змінна maximum, що є результатом роботи потоку;
  • конструктор. У конструкторі здійснюється початкова ініціалізація масиву а також створення та запуск дочірнього потоку. Запуск потоку виконується методом start(). Якщо в конструкторі не реалізувати створення та запуск дочірнього потоку, то методи екземпляру класу будуть виконуватись в основному потоці;
  • метод run(), який визначається в інтерфейсі Runnable. Оскільки клас Thread1 реалізує цей інтерфейс, то в ньому потрібно обов’язково реалізовувати метод run(). У методі run() вписується основний код розв’язку нашої задачі – код пошуку максимального значення;
  • методи доступу до полів класу, значення яких використовується в основному потоці.

У даній задачі основним потоком служить функція main(), в якій потрібно виконати такі кроки:

  • оголосити тестувальний масив;
  • створити дочірній потік (екземпляр класу Thread1) та отримати посилання на нього;
  • запустити метод join() для того, щоб основний потік завершився останнім;
  • після завершення дочірнього потоку прочитати результат, отриманий в цьому потоці.

Текст роботи програми наступний.

// Клас, що реалізує дочірній потік у даній задачі
class Thread1 implements Runnable {

  private Thread thr; // посилання на дочірній потік
  private int[] AI;
  private int maximum; // Результат роботи потоку

  // Конструктор - отримує масив цілих чисел
  public Thread1(int[] _AI) {
    // Ініціалізація масиву
    AI = _AI;

    // Створити потік
    thr = new Thread(this, "Thread1.");

    // Запустити потік виконання
    thr.start();
  }

  // Метод, в якому вписується код роботи потоку.
  // У нашому випадку вписується код пошуку мінімального значення та
  // заповнення змінної maximum.
  public void run() {
    int max = AI[0];
    for (int i=1; i<AI.length; i++)
      if (max<AI[i]) max = AI[i];
    maximum = max;
  }

  // Методи доступу до полів класу
  public Thread getThread() { return thr; }
  public int getMax() { return maximum; }
}

public class TrainThreads2 {

  public static void main(String[] args) {
    // 1. Оголосити тестувальний масив
    int[] AI = { 2, 3, 4, 8, -1 };

    // 2. Створити дочірній потік, отримати посилання на нього
    Thread1 t1 = new Thread1(AI);

    // 3. Зчитати результат
    try {
      // Очікування завершення потоку t1 - обов'язково,
      // інакше будуть отримані нулі
      t1.getThread().join();
    }
    catch (InterruptedException e) {
      System.out.println("Error.");
    }

    // Зчитати результат після завершення потоку t1
    System.out.println("max = " + t1.getMax());
  }
}

Якщо у функції main() прибрати код

try {
  // Очікування завершення потоку t1 - обов'язково,
  // інакше будуть отримані нулі
  t1.getThread().join();
}
catch (InterruptedException e) {
  System.out.println("Error.");
}

то буде отримано наступний результат

max = 0

Це означає, що основний потік завершується першим, не дочекавшись завершення дочірнього потоку. У цьому випадку з дочірнього потоку повертається початкове значення змінної maximum в екземплярі t1. А це є помилкою.

Результат виконання програми

max = 8

 

3. Метод isAlive(). Визначити, чи виконується потік. Приклад

Метод isAlive() призначений для визначення існування (виконання) потоку. Загальна форма методу наступна:

final boolean isAlive()

Метод повертає true, якщо потік, для якого викликаний метод, ще виконується. В іншому випадку він повертає false.

Приклад. У прикладі з допомогою методу isAlive() очікується завершення дочірнього потоку з метою отримання результату. У дочірньому потоці обчислюється сума елементів масиву чисел типу double. Щоб отримати коректну суму, потрібно щоб головний потік завершився останнім.

// Клас, що інкапсулює потік виконання
class SumArray implements Runnable {
  Thread thr; // посилання на поточний потік
  double[] AD; // посилання на внутрішній масив
  double summ; // результат роботи потоку

  // Конструктор класу SumArray - отримує назву потоку та деякий масив
  SumArray(double[] AD, String name) {
    // Присвоїти масиву AD зовнішнє посилання
    this.AD = AD;

    // Створити екземпляр thr і встановити йому назву потоку
    thr = new Thread(this, name);

    // Запустити потік на виконання
    thr.start(); // викликається метод run()
  }

  // Точка входу в потік - метод run()
  public void run() {
    // Повідомлення про початок виконання потоку - обчислення суми масиву
    System.out.println("SumArray.run() - begin");

    // у потоці обчислити суму елементів масиву AD
    // та записати її в змінну summ
    summ = 0;

    for (int i=0; i<AD.length; i++) {
      try {
        // зробити паузу, щоб могли отримати керування інші потоки
        Thread.sleep(50);
        summ = summ + AD[i];
      }
      catch (InterruptedException e) {
        System.out.println(e.getMessage());
      }
    }

    // Повідомлення про завершення потоку
    System.out.println("SumArray.run() - end");
  }

  // Метод, що повертає поточний потік
  Thread getThread() {
    return thr;
  }

  // Метод, що повертає суму елементів
  double getSumm() {
    return summ;
  }
}

public class Threads {

  public static void main(String[] args) {
    // Демонстрація методу isAlive()
    // 1. Оголосити деякий масив, для якого потрібно обчислити суму
    double[] AD = { 1.1, 2.2, 3.3, 4.4, 5.5, 6.6 };

    // 2. Створити екземпляр класу SumArray
    SumArray sa = new SumArray(AD, "SumArray thread"); // запускається потік

    // 3. Викликати метод isAlive() в циклі для того,
    //    щоб дочекатись закінчення потоку sa
    while (sa.getThread().isAlive());

    // 4. Після закінчення дочірнього потоку отримати суму
    double summ = sa.getSumm();
    System.out.println("summ = " + summ);
  }
}

У вищенаведеному прикладі, затримка здійснюється з допомогою виклику

while (sa.getThread().isAlive());

Якщо закоментувати цей рядок, то програма може видати нульове значення суми. Це пов’язано з тим, що головний потік завершиться до завершення дочірнього потоку.

Результат виконання програми

SumArray.run() - begin
SumArray.run() - end
summ = 23.1

 

4. Методи setPriority(), getPriority(). Встановити та отримати пріоритет потоку. Приклад

Як відомо, для потоків виконання може бути встановлено різний пріоритет. Потоки з більшим пріоритетом отримують більшу частину часу центрального процесора на своє виконання.

У мові Java існує можливість встановлення пріоритету потоків. Для цього використовуються два методи:

  • setPriority() – встановлює значення пріоритету потоку;
  • getPriority() – читає (отримує) значення пріоритету потоку.

В документації Java загальна форма методів наступна:

final void setPriority(int level)
final int getPriority()

тут

  • level – рівень пріоритету, який задається в межах констант від MIN_PRIORITY до MAX_PRIORITY. Значення MIN_PRIORITY = 1. Значення MAX_PRIORITY = 10. Якщо потрібно встановити за замовчуванням значення пріоритету, то для цього використовується статична константа NORM_PRIORITY, яка рівна 5.

Приклад. У прикладі демонструється створення трьох потоків з різними пріоритетами. В кожному потоці створюється масив випадкових цілих чисел. Для інкапсуляції потоку реалізовано клас RandomArray, який містить наступні складові:

  • внутрішнє поле thr – посилання на поточний потік виконання;
  • внутрішнє поле AI – посилання на масив чисел;
  • конструктор RandomArray(). У конструкторі задається пріоритет потоку та здійснюється запуск потоку на виконання;
  • метод run(), в якому генерується масив випадкових чисел. Цей метод є точкою входу в потік;
  • методи доступу до внутрішніх полів getThread() та getArray();
  • метод getPriority(), що повертає пріоритет потоку виконання. Цей метод звертається до однойменного методу екземпляру thr для отримання числового значення пріоритету.

 

// Клас, що інкапсулює потік виконання
class RandomArray implements Runnable {
  private Thread thr; // посилання на поточний потік
  private int[] AI; // масив чисел

  // Конструктор класу SumArray.
  // Конструктор отримує параметри:
  // - name - назва потоку;
  // - size - розмір масиву, який потрібно згенерувати в потоці;
  // - priority - пріоритет потоку.
  RandomArray(String name, int size, int priority) {

    // 1. Створити екземпляр потоку thr і задати йому назву потоку
    thr = new Thread(this, name);

    // 2. Виділити пам'ять для масиву AI
    AI = new int[size];

    // 3. Встановити пріоритет
    //    Перевірка значення priority на коректність
    if ((priority>=Thread.MIN_PRIORITY)&&(priority<=Thread.MAX_PRIORITY))
      thr.setPriority(priority);
    else
      // якщо некоректне priority, то встановити пріоритет за замовчуванням
      thr.setPriority(Thread.NORM_PRIORITY);

    // Запустити потік на виконання
    thr.start(); // викликається метод run() - генерується масив чисел
  }

  // Точка входу в потік - метод run()
  public void run() {
    // Повідомлення про початок виконання потоку - вказується ім'я потоку
    System.out.println(thr.getName() + " - begin");

    // у потоці заповнити масив цілих чисел значеннями від 1 до AI.length
    for (int i=0; i<AI.length; i++) {
      try {
        // зробити паузу, щоб могли отримати керування інші потоки
        Thread.sleep(10);

        AI[i] = (int)(Math.random()*AI.length+1);
      }
      catch (InterruptedException e) {
        System.out.println(e.getMessage());
      }
    }

    // Повідомлення про завершення потоку
    System.out.println(thr.getName() + " - end");
  }

  // Метод, що повертає посилання на поточний потік
  Thread getThread() {
    return thr;
  }

  // Метод, що повертає посилання на масив чисел
  int[] getArray() {
    return AI;
  }

  // Метод, що повертає пріоритет потоку
  int getPriority() {
    // викликається getPriority()
    return thr.getPriority();
  }

  // Метод, що виводить масив на екран
  void Print(String text) {
    System.out.print(text + ".");
    System.out.print("AI = ");
    for (int i=0; i<AI.length; i++) {
      System.out.print(AI[i] + " ");
    }
    System.out.println();
  }
}

public class Threads {

  public static void main(String[] args) {

    try {
      // 1. Створити три потоки з різними пріоритетами
      RandomArray A1 = new RandomArray("A1", 1000, 7);
      RandomArray A2 = new RandomArray("A2", 1000, 2);
      RandomArray A3 = new RandomArray("A3", 1000, 22);

      // Дочекатись завершення потоків
      A1.getThread().join();
      A2.getThread().join();
      A3.getThread().join();

      // 2. Вивести пріоритети потоків для контролю
      System.out.println("A1 priority = " + A1.getPriority());
      System.out.println("A2 priority = " + A2.getPriority());
      System.out.println("A3 priority = " + A3.getPriority());
    }
    catch (InterruptedException e) {
      System.out.println(e.getMessage());
    }
  }
}

Результат виконання програми

A2 - begin
A3 - begin
A1 - begin
A1 - end
A3 - end
A2 - end
A1 priority = 7
A2 priority = 2
A3 priority = 5

 


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