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

 


Связанные темы