Java является многопоточным языком программирования, а это значит, что с помощью него мы можем разрабатывать многопоточные программы. Многопоточная программа состоит из двух или более частей, которые могут выполняться одновременно, и каждая часть может одновременно обрабатывать разные задачи, оптимально используя доступные ресурсы, особенно если компьютер имеет несколько ЦП.

По определению, многозадачность – это совместное использование несколькими процессами общих ресурсов обработки, таких как ЦП. Многопоточность расширяет идею многозадачности на приложения, в которых вы можете разделить определенные операции в рамках одного приложения на отдельные потоки. Каждый из потоков может работать параллельно. ОС распределяет время обработки не только между различными приложениями, но и между каждым потоком в приложении.

Многопоточность позволяет создавать код таким образом, чтобы несколько действий могли выполняться одновременно в одной программе.

Жизненный цикл потока

В своем жизненном цикле поток проходит различные стадии. Например, поток создается, запускается, выполняется и затем останавливается. На следующей схеме показан полный жизненный цикл потока.

Жизненный цикл потока

Ниже приведены этапы жизненного цикла:

  • Новый− Новый поток начинает свой жизненный цикл в состоянии нового. Он сохраняет это состояние до тех пор, пока программа не запустит поток. Его также называют созданным потоком.
  • Запущенный − После запуска нового потока он становится запущенным. Считается, что поток в этом состоянии выполняет свою задачу.
  • Ожидающий − Иногда поток переходит в состояние ожидания, т.е. поток ожидает, пока другой поток выполнит задачу. Поток переходит обратно в запущенное состояние только после того, когда другой поток сигнализирует ожидающему потоку продолжить выполнение.
  • Ожидающий с ограничением по времени − Запущенный поток может войти в состояние ожидания с ограничением по времени в течение определенного интервала времени. Поток в этом состоянии переходит обратно в запущенное состояние, когда истекает этот временной интервал или когда происходит событие, которого он ожидает.
  • Остановленный− Запущенный поток переходит в остановленное состояние, когда он завершает свою задачу или иным образом завершается.

Приоритеты потоков

Каждый поток Java имеет приоритет, который помогает операционной системе определять порядок, в котором планируются потоки.

Приоритеты потоков Java находятся в диапазоне от MIN_PRIORITY (константа 1) до MAX_PRIORITY (константа 10). По умолчанию каждому потоку устанавливается приоритет NORM_PRIORITY (константа 5).

Потоки с более высоким приоритетом более важны для программы, и в первую очередь им должно выделяться процессорное время. Однако приоритеты потоков не могут гарантировать порядок, в котором выполняются потоки, и очень сильно зависят от платформы.

Создание потока путем реализации интерфейса Runnable

Если ваш класс предназначен для выполнения как поток, вы можете добиться этого, реализовав интерфейс Runnable. Вам нужно будет выполнить три основных шага:

Шаг 1

В качестве первого шага вам необходимо реализовать метод run(), предоставляемый интерфейсом Runnable. Этот метод обеспечивает точку входа для потока, и вы поместите в него полный код, реализующий функциональность приложения. Ниже приводится простой синтаксис метода run().

public void run( )

Шаг 2

На втором шаге вы создадите экземпляр объекта Thread, используя следующий конструктор:

Thread(Runnable threadObj, String threadName);

Где threadObj - это экземпляр класса, который реализует интерфейс Runnable, а threadName - это имя, данное новому потоку.

Шаг 3

После создания объекта Thread вы можете запустить его, вызвав метод start(), который выполняет вызов метода run(). Ниже приводится простой синтаксис метода start(). 

void start();

Пример

Вот пример, который создает новый поток и запускает его:

class RunnableDemo implements Runnable {
   private Thread t;
   private String threadName;
   
   RunnableDemo( String name) {
      threadName = name;
      System.out.println("Создание " +  threadName );
   }
   
   public void run() {
      System.out.println("Выполнение " +  threadName );
      try {
         for(int i = 4; i > 0; i--) {
            System.out.println("Поток: " + threadName + ", " + i);
            // Пусть поток немного подождет.
            Thread.sleep(50);
         }
      } catch (InterruptedException e) {
         System.out.println("Поток " +  threadName + " прерван.");
      }
      System.out.println("Поток " +  threadName + " завершается.");
   }
   
   public void start () {
      System.out.println("Запуск " +  threadName );
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
   }
}

public class TestThread {

   public static void main(String args[]) {
      RunnableDemo R1 = new RunnableDemo( "Поток-1");
      R1.start();
      
      RunnableDemo R2 = new RunnableDemo( " Поток-2");
      R2.start();
   }   
}

Это даст следующий результат:

Создание Поток-1
Запуск Поток-1
Создание Поток-2
Запуск Поток-2
Выполнение Поток-1
Поток: Поток-1, 4
Выполнение Поток-2
Поток: Поток-2, 4
Поток: Поток-1, 3
Поток: Поток-2, 3
Поток: Поток-1, 2
Поток: Поток-2, 2
Поток: Поток-1, 1
Поток: Поток-2, 1
Поток Поток-1 завершается.
Поток Поток-2 завершается.

Создание потока путем расширения класса Thread

Второй способ создания потока в Java заключается в создании нового класса, расширяющего класс Thread посредством следующих двух простых шагов. Этот подход обеспечивает большую гибкость в обработке нескольких потоков, созданных с использованием доступных методов в классе Thread.

Шаг 1

Вам нужно будет переопределить метод run(), доступный в классе Thread. Этот метод обеспечивает точку входа для потока, и вы поместите в него полный код, реализующий функциональность приложения. Ниже приводится простой синтаксис метода run().

public void run( )

Шаг 2

После создания объекта Thread вы можете запустить его, вызвав метод start(), который выполняет вызов метода run(). Ниже приведен простой синтаксис метода start():

void start( );

Пример

Вот предыдущая программа, переписанная для расширения потока:

class ThreadDemo extends Thread {
   private Thread t;
   private String threadName;
   
   ThreadDemo( String name) {
      threadName = name;
      System.out.println("Создание " +  threadName );
   }
   
   public void run() {
      System.out.println("Выполнение " +  threadName );
      try {
         for(int i = 4; i > 0; i--) {
            System.out.println("Поток: " + threadName + ", " + i);
            // Пусть поток немного подождет.
            Thread.sleep(50);
         }
      } catch (InterruptedException e) {
         System.out.println("Поток " + threadName + " прерывается.");
      }
      System.out.println("Поток " +  threadName + " завершается.");
   }
   
   public void start () {
      System.out.println("Запуск " +  threadName );
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
   }
}

public class TestThread {

   public static void main(String args[]) {
      ThreadDemo T1 = new ThreadDemo( "Поток-1");
      T1.start();
      
      ThreadDemo T2 = new ThreadDemo( "Поток-2");
      T2.start();
   }   
}

Это даст следующий результат:

Создание Поток-1
Запуск Поток-1
Создание Поток-2
Запуск Поток-2
Выполнение Поток-1
Поток: Поток-1, 4
Выполнение Поток-2
Поток: Поток-2, 4
Поток: Поток-1, 3
Поток: Поток-2, 3
Поток: Поток-1, 2
Поток: Поток-2, 2
Поток: Поток-1, 1
Поток: Поток-2, 1
Поток Поток-1 завершается.
Поток Поток-2 завершается.

Методы потока

Ниже приводится список важных методов, доступных в классе Thread в Java.

Метод и описание

1

public void start()
Запускает поток по отдельному пути выполнения, затем вызывает метод run() для этого объекта Thread.

2

public void run()
Если этот объект Thread был создан с использованием отдельной цели Runnable, метод run() вызывается для этого объекта Runnable.

3

public final void setName(String name)
Изменяет имя объекта Thread. Также существует метод getName() для получения имени.

4

public final void setPriority(int priority)
Устанавливает приоритет этого объекта Thread. Возможные значения от 1 до 10.

5

public final void setDaemon(boolean on)
Параметр true обозначает этот поток как вспомогательный поток.

6

public final void join(long millisec)
Текущий поток вызывает этот метод во втором потоке, в результате чего текущий поток блокируется до тех пор, пока второй поток не завершится или не пройдет заданное количество миллисекунд.

7

public void interrupt()
Прерывает этот поток, заставляя его продолжить выполнение, если он был заблокирован по какой-либо причине.

8

public final boolean isAlive()
Возвращает true, если поток активен, то есть в любое время после запуска потока, но до его завершения.

Предыдущие методы вызываются для конкретного объекта Thread. Следующие методы в классе Thread являются статическими. Вызов одного из статических методов выполняет операцию в текущем запущенном потоке.

Метод и описание

1

public static void yield()
Заставляет текущий запущенный поток уступить место любым другим потокам с тем же приоритетом, которые ожидают планирования.

2

public static void sleep(long millisec)
Блокирует текущий запущенный поток по крайней мере на указанное количество миллисекунд.

3

public static boolean holdsLock(Object x)
Возвращает true, если текущий поток удерживает блокировку данного объекта.

4

public static Thread currentThread()
Возвращает ссылку на текущий запущенный поток, который вызывает этот метод.

5

public static void dumpStack()
Выводит отслеживание стека для текущего запущенного потока, что полезно при отладке многопоточного приложения.

Пример

Следующая программа ThreadClassDemo демонстрирует некоторые из этих методов класса Thread. Рассмотрим класс DisplayMessage, который реализует Runnable:

// Название файла: DisplayMessage.java
// Создаем поток для реализации Runnable

public class DisplayMessage implements Runnable {
   private String message;
   
   public DisplayMessage(String message) {
      this.message = message;
   }
   
   public void run() {
      while(true) {
         System.out.println(message);
      }
   }
}

Ниже приведен еще один класс, который расширяет класс Thread.

// Название файла: GuessANumber.java
// Создаем поток для расширения Thread

public class GuessANumber extends Thread {
   private int number;
   public GuessANumber(int number) {
      this.number = number;
   }
   
   public void run() {
      int counter = 0;
      int guess = 0;
      do {
         guess = (int) (Math.random() * 100 + 1);
         System.out.println(this.getName() + " угадываний " + guess);
         counter++;
      } while(guess != number);
      System.out.println("** Верно!" + this.getName() + "за" + counter + "угадываний.**");
   }
}

Ниже приведена основная программа, в которой используются выше определенные классы.

// Название файла: ThreadClassDemo.java
public class ThreadClassDemo {

   public static void main(String [] args) {
      Runnable hello = new DisplayMessage("Привет");
      Thread thread1 = new Thread(hello);
      thread1.setDaemon(true);
      thread1.setName("привет");
      System.out.println("Запуск потока привет...");
      thread1.start();
      
      Runnable bye = new DisplayMessage("Пока");
      Thread thread2 = new Thread(bye);
      thread2.setPriority(Thread.MIN_PRIORITY);
      thread2.setDaemon(true);
      System.out.println("Запуск потока пока...");
      thread2.start();

      System.out.println("Запуск потока3...");
      Thread thread3 = new GuessANumber(27);
      thread3.start();
      try {
         thread3.join();
      } catch (InterruptedException e) {
         System.out.println("Поток прерван.");
      }
      System.out.println("Запуск потока4...");
      Thread thread4 = new GuessANumber(75);
      
      thread4.start();
      System.out.println("main() завершается...");
   }
}

Это даст следующий результат. Вы можете пробовать этот пример снова и снова, и каждый раз вы будете получать разные результаты.

Вывод
Запуск потока привет...
Запуск потока пока...
Привет
Привет
Привет
Привет
Привет
Привет
Пока
Пока
Пока
Пока
Пока
.......

Основные концепции многопоточности Java

При выполнении многопоточного программирования на Java вы столкнетесь со следующими вопросами:

Оглавление