Руководство по объединению потоков в Java

В этом уроке мы узнаем, как объединить два потока и почему необходимо объединять потоки в java. Мы подробно рассмотрим API Thread.join() и различные его версии вместе с практическими примерами.

Мы также увидим, как можно попасть в ситуацию взаимоблокировки при использовании join(), а также способы эффективного использования этого метода в нашем коде.

1. Введение в соединение нитей

Поток — это легкий процесс, который позволяет программе работать более эффективно, запуская несколько потоков параллельно. Если у нас есть требование, при котором первый поток должен ждать, пока второй поток не завершит свое выполнение, то мы должны объединить эти 2 потока.

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

Руководство по присоединению потоков в Java0
  • Распространение свадебных открыток начинается сразу после завершения печати свадебных открыток, а сама печать свадебных открыток начинается сразу после определения места проведения свадьбы.
  • Можно сказать, что в этом случае Поток-3 должен ждать, пока Поток-2 завершит свое выполнение, а Поток-2 также должен ждать, пока Поток-1 завершит свое выполнение.
  • Поток-2 должен вызвать Thread-1.join(), а Поток-3 должен вызвать Thread-2.join(), чтобы они дождались завершения другого потока.

2. API-интерфейс Java Thread.join()

Метод join() переводит вызывающий поток в состояние ожидания, пока поток, в котором вызывается join(), не завершит свое выполнение.

  • Поток «t1» хочет дождаться, пока другой поток «t2» завершит свое выполнение, после чего t1 должен вызвать метод join() для t2,
  • t2.join() вызывается t1.
  • Когда t1 выполняет t2.join(), то t1 немедленно переходит в состояние ожидания и продолжает ждать, пока t2 не завершит свое выполнение и не завершится.
  • После завершения t2 снова продолжает выполнение только t1.
присоединение к потоку
Жизненный цикл присоединенного потока

2.1 Присоединение без тайм-аута

Метод join() по умолчанию заставляет текущий поток ждать бесконечно, пока поток, на котором он вызван, не завершится. Если поток прерывается, он выбрасывает InterruptedException.

public final void join() throws InterruptedException

Давайте теперь рассмотрим пример использования метода join().

public class ChildThread extends Thread{public void run(){for(int i=1; i<=4; i++){try{Thread.sleep(500);} catch(Exception e){System.out.println(e);}System.out.println(“child thread execution - ” + i);}}}
ChildThread th1 = new ChildThread();// Starting child Threadth1.start();// main thread joining the child threadth1.join();// main Thread printing statementsSystem.out.println(“main thread completed”);

Обратите внимание на вывод программы. Основной поток вызывает метод join() в дочернем потоке и ждет его завершения, поэтому после вызова join(). Таким образом, все операторы из дочернего потока печатаются перед операторами основного потока.

child thread execution - 1child thread execution - 2child thread execution - 3child thread execution – 4main thread completed

2.2 Присоединение с тайм-аутом

  • join(long millis): заставляет текущий поток ждать, пока поток, в котором он вызван, не завершится или не истечет время, указанное в миллисекундах.
  • join(long millis, int nanos): заставляет текущий поток ждать, пока поток, в котором он вызван, не завершится или не истечет время, указанное в миллисекундах + наносекундах.
public final synchronized void join(long millis) throws InterruptedExceptionpublic final synchronized void join(long millis, int nanos) throws InterruptedException

Давайте теперь рассмотрим, как это работает, на примере.

// Creating child Thread ObjectChildThread th1 = new ChildThread();// Starting child Threadth1.start();//main thread joining the child thread with 1000ms expiration timeth1.join(1000);// main Thread printing statementsSystem.out.println(“main thread completed”);

При выполнении программы основной поток будет ожидать максимум 1000 мс и будет вызван в любое время после этого.

child thread execution - 1main thread completedchild thread execution - 2child thread execution - 3child thread execution - 4

3. Прерывание присоединенной темы

Ожидающий поток может быть прерван другим потоком с помощью метода interrupt(). Как только ожидающий поток прерывается, он немедленно выходит из состояния ожидания и переходит в состояние Ready/Runnable и ждет, пока планировщик потоков выделит процессор для повторного начала выполнения.

public class ThreadJoiningInterrupt {public static void main(String[] args) {ChildThread childThread = new ChildThread(Thread.currentThread());childThread.start();try {childThread.join(); //Joined} catch(InterruptedException ie) {System.out.println("main Thread is interrupted");}for(int i = 1; i <= 4; i++) {System.out.println("main Thread Execution - " + i);}}}class ChildThread extends Thread {private static Thread parentThreadRef;public ChildThread(Thread parentThreadRef) {this.parentThreadRef = parentThreadRef;}public void run() {parentThreadRef.interrupt(); //Interruptedfor(int i = 1; i <= 4; i++) {System.out.println("Child Thread Execution - " + i);}}}

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

Child Thread Execution - 1main Thread is interruptedChild Thread Execution - 2main Thread Execution - 1main Thread Execution - 2main Thread Execution - 3main Thread Execution - 4Child Thread Execution - 3Child Thread Execution – 4

4. Остерегайтесь тупиков

Мы должны быть осторожны при использовании метода join(), так как иногда это может привести к ситуации взаимоблокировки. Если два потока вызывают метод join() друг у друга, то оба потока переходят в состояние ожидания и ждут друг друга вечно.

Аналогично, если поток по ошибке присоединится сам к себе, то он будет заблокирован навсегда, и для восстановления работы JVM придется остановить работу.

class Threadjoindemo{public static void main(String[] args){// main thread calling join() on itselfThread.currentThread().join(); // Program gets stuck, Deadlock suitation}}

5. Заключение

В этом уроке мы узнали о необходимости объединения двух потоков и о том, как использовать API Thread.join() с или без срока действия. Мы также увидели, как можно прервать присоединенный поток. Мы также узнали, как избегать ситуаций взаимоблокировки при использовании join().

Приятного обучения!!

Прокрутить вверх