Учебное пособие по многопоточности в Java с программами и примерами


Любое приложение может иметь несколько процессов (экземпляров). Каждый из этих процессов может быть назначен либо как один поток, либо как несколько потоков. В этом руководстве мы увидим, как выполнять несколько задач одновременно, а также узнаем больше о потоках и синхронизации между потоками.

В этом уроке по многопоточности в Java мы узнаем:

Что такое одиночная нить?

Один поток в Java — это, по сути, легкая и наименьшая единица обработки. Java использует потоки с помощью «класса потока».

Существует два типа ниток – пользовательский поток и поток демона (потоки демона используются, когда мы хотим очистить приложение, и используются в фоновом режиме).

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

Пример одного потока:

package demotest;

public class GuruThread
{
       public static void main(String[] args) {
              System.out.println("Single Thread");
       }
}

Преимущества однопоточного:

  • Снижает накладные расходы в приложении, поскольку в системе выполняется один поток.
  • Кроме того, это снижает затраты на обслуживание приложения.

Что такое многопоточность в Java?

многопоточность в Java — это процесс одновременного выполнения двух или более потоков.neoобычно для максимального использования процессора. Многопоточные приложения выполняют два или более потоков одновременно. Следовательно, он также известен как параллелизм в Java. Каждый поток работает параллельно друг другу. Несколько потоков не выделяют отдельную область памяти, следовательно, они экономят память. Кроме того, переключение контекста между потоками занимает меньше времени.

Пример многопоточности:

package demotest;
public class GuruThread1 implements Runnable
{
       public static void main(String[] args) {
        Thread guruThread1 = new Thread("Guru1");
        Thread guruThread2 = new Thread("Guru2");
        guruThread1.start();
        guruThread2.start();
        System.out.println("Thread names are following:");
        System.out.println(guruThread1.getName());
        System.out.println(guruThread2.getName());
    }
    @Override
    public void run() {
    }
}

Преимущества многопоточности:

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

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

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

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

Существуют различные этапы жизненного цикла потока, как показано на диаграмме выше:

  1. Новые
  2. Работоспособен
  3. Бег
  4. Ожидание
  5. мертв
  1. Новое: На этом этапе поток создается с использованием класса «Класс потока». Он остается в этом состоянии до тех пор, пока программа не начинается нить. Ее еще называют рожденной нитью.
  2. Работоспособен: На этой странице экземпляр потока вызывается с помощью метода start. Управление потоком передается планировщику для завершения выполнения. От планировщика зависит, запускать ли поток.
  3. Бег: Когда поток начинает выполняться, его состояние меняется на «работающее». Планировщик выбирает один поток из пула потоков и начинает его выполнение в приложении.
  4. Ожидание: Это состояние, когда потоку приходится ждать. Поскольку в приложении выполняется несколько потоков, существует необходимость синхронизации между потоками. Следовательно, один поток должен ждать, пока другой поток не выполнится. Поэтому это состояние называется состоянием ожидания.
  5. Мертвая: Это состояние, когда поток завершается. Поток находится в рабочем состоянии, и как только он завершил обработку, он находится в «мертвом состоянии».


Некоторые из часто используемых методов для потоков:

Способ доставки Описание
Начало() Этот метод запускает выполнение потока и JVM вызывает метод run() в потоке.
Сон (целое число миллисекунд) Этот метод переводит поток в спящий режим, поэтому выполнение потока приостанавливается на предоставленные миллисекунды, и после этого поток снова начинает выполнение. Это помогает в синхронизации потоков.
GetName () Он возвращает имя потока.
setPriority (интервал новый приоритет) Это меняет приоритет потока.
урожай () Это приводит к остановке текущего потока и выполнению других потоков.

Пример: В этом примере многопоточной программы на Java мы собираемся создать поток и изучить встроенные методы, доступные для потоков.

package demotest;
public class thread_example1 implements Runnable {
    @Override
    public void run() {
    }
    public static void main(String[] args) {
        Thread guruthread1 = new Thread();
        guruthread1.start();
        try {
            guruthread1.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        guruthread1.setPriority(1);
        int gurupriority = guruthread1.getPriority();
        System.out.println(gurupriority);
        System.out.println("Thread Running");
  }
}

Расшифровка кода:

  • Строка кода 2: Мы создаем класс «thread_Example1», который реализует интерфейс Runnable (он должен быть реализован любым классом, экземпляры которого предназначены для выполнения потоком).
  • Строка кода 4: Он переопределяет метод запуска работающего интерфейса, поскольку переопределение этого метода является обязательным.
  • Строка кода 6: Здесь мы определили основной метод, в котором начнем выполнение потока.
  • Строка кода 7: Здесь мы создаем новое имя потока как «guruthread1», создавая экземпляр нового класса потока.
  • Строка кода 8: мы будем использовать метод «start» потока, используя экземпляр «guruthread1». Здесь поток начнет выполняться.
  • Строка кода 10: Здесь мы используем метод «sleep» потока, использующего экземпляр «guruthread1». Следовательно, поток будет спать в течение 1000 миллисекунд.
  • Код 9-14: Здесь мы поместили метод сна в блок try catch, поскольку возникает проверенное исключение, т. е. прерванное исключение.
  • Строка кода 15: Здесь мы устанавливаем приоритет потока на 1, независимо от того, какой приоритет у него был.
  • Строка кода 16: Здесь мы получаем приоритет потока с помощью getPriority().
  • Строка кода 17: Здесь мы печатаем значение, полученное из getPriority.
  • Строка кода 18: Здесь мы пишем текст о том, что поток запущен.

Когда вы выполните приведенный выше код, вы получите следующееwing вывод:

Пример потока на Java

Вывод:

5 — это приоритет потока, а Thread Running — это текст, который является результатом нашего кода.

Синхронизация потоков Java

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

Когда существует необходимость доступа к общим ресурсам двумя или более потоками, используется подход синхронизации.

Java предоставила синхронизированные методы для реализации синхронизированного поведения.

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

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

Это можно написать нижеwing форма:

Synchronized(object)
{  
        //Block of statements to be synchronized
}

Пример многопоточности Java

В этом примере многопоточной Java мы возьмем два потока и получим имена потоков.

Example1:

GuruThread1.java
package demotest;
public class GuruThread1 implements Runnable{

    /**
     * @param args
     */
    public static void main(String[] args) {
        Thread guruThread1 = new Thread("Guru1");
        Thread guruThread2 = new Thread("Guru2");
        guruThread1.start();
        guruThread2.start();
        System.out.println("Thread names are following:");
        System.out.println(guruThread1.getName());
        System.out.println(guruThread2.getName());
    }
    @Override
    public void run() {
    }
}

Расшифровка кода:

  • Строка кода 3: Мы взяли класс GuruThread1, который реализует Runnable (он должен быть реализован любым классом, экземпляры которого предназначены для выполнения потоком).
  • Строка кода 8: Это основной метод класса
  • Строка кода 9: Здесь мы создаем экземпляр класса Thread, создаем экземпляр с именем «guruThread1» и создаем поток.
  • Строка кода 10: Здесь мы создаем экземпляр класса Thread, создаем экземпляр с именем «guruThread2» и создаем поток.
  • Строка кода 11: Мы запускаем поток, т.е. guruThread1.
  • Строка кода 12: Мы запускаем поток, т.е. guruThread2.
  • Строка кода 13: Вывод текста в виде «Имена тем следующие».wing: "
  • Строка кода 14: Получение имени потока 1 с помощью метода getName() класса потока.
  • Строка кода 15: Получение имени потока 2 с помощью метода getName() класса потока.

Когда вы выполните приведенный выше код, вы получите следующееwing вывод:

Пример многопоточности Java

Вывод:

Имена потоков выводятся здесь как

  • Guru1
  • Guru2

Пример 2:

В этом примере многопоточности в Java мы узнаем о переопределении методов run() и start() работающего интерфейса, создадим два потока этого класса и запустим их соответствующим образом.

Кроме того, мы учимся на двух занятиях,

  • Тот, который будет реализовывать работоспособный интерфейс и
  • Еще один, который будет иметь основной метод и выполняться соответствующим образом.
package demotest;
public class GuruThread2 {
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  GuruThread3 threadguru1 = new GuruThread3("guru1");
  threadguru1.start();
  GuruThread3 threadguru2 = new GuruThread3("guru2");
  threadguru2.start();
 }
}
class GuruThread3 implements Runnable {
 Thread guruthread;
 private String guruname;
 GuruThread3(String name) {
  guruname = name;
 }
 @Override
 public void run() {
  System.out.println("Thread running" + guruname);
  for (int i = 0; i < 4; i++) {
   System.out.println(i);
   System.out.println(guruname);
   try {
    Thread.sleep(1000);
   } catch (InterruptedException e) {
    System.out.println("Thread has been interrupted");
   }
  }
 }
 public void start() {
  System.out.println("Thread started");
  if (guruthread == null) {
   guruthread = new Thread(this, guruname);
   guruthread.start();
  }
 }
}

Расшифровка кода:

  • Строка кода 2: Здесь мы берем класс GuruThread2, в котором будет основной метод.
  • Строка кода 4: Здесь мы берем основной метод класса.
  • Строка кода 6–7: Здесь мы создаем экземпляр класса GuruThread3 (который создается в строках кода ниже) как «threadguru1» и запускаем поток.
  • Строка кода 8–9: Здесь мы создаем еще один экземпляр класса GuruThread3 (который создается в строках кода ниже) как «threadguru2» и запускаем поток.
  • Строка кода 11: Здесь мы создаем класс «GuruThread3», который реализует работоспособный интерфейс (он должен быть реализован любым классом, экземпляры которого предназначены для выполнения потоком).
  • Строка кода 13–14: мы берем две переменные класса, одна из которых относится к классу потока, а другая — к классу строк.
  • Строка кода 15–18: мы переопределяем конструктор GuruThread3, который принимает один аргумент в качестве строкового типа (имя потока), который присваивается переменной класса guruname и, следовательно, сохраняется имя потока.
  • Строка кода 20: Здесь мы переопределяем метод run() работающего интерфейса.
  • Строка кода 21: Мы выводим имя потока с помощью оператора println.
  • Строка кода 22–31: Здесь мы используем цикл for со счетчиком, инициализированным равным 0, и он не должен быть меньше 4 (мы можем взять любое число, поэтому здесь цикл будет выполняться 4 раза) и увеличиваем счетчик. Мы печатаем имя потока, а также переводим его в спящий режим на 1000 миллисекунд в блоке try-catch, поскольку метод сна вызывает проверенное исключение.
  • Строка кода 33: Здесь мы переопределяем метод запуска работающего интерфейса.
  • Строка кода 35: Выводим текст «Тема запущена».
  • Строка кода 36–40: Здесь мы используем условие if, чтобы проверить, имеет ли переменная класса guruthread значение или нет. Если его значение равно нулю, мы создаем экземпляр, используя класс потока, который принимает имя в качестве параметра (значение для которого было присвоено в конструкторе). После чего поток запускается с помощью метода start().

Когда вы выполните приведенный выше код, вы получите следующееwing вывод:

Пример многопоточности в Java

Результат:

Есть два потока, следовательно, мы дважды получаем сообщение «Поток запущен».

Мы получаем имена потоков в том виде, в каком мы их вывели.

Он входит в цикл for, где мы печатаем счетчик и имя потока, а счетчик начинается с 0.

Цикл выполняется три раза, а между ними поток приостанавливается на 1000 миллисекунд.

Следовательно, сначала мы получаем guru1, затем guru2, затем снова guru2, потому что поток спит здесь на 1000 миллисекунд, а затем следующий guru1 и снова guru1, поток спит 1000 миллисекунд, поэтому мы получаем guru2, а затем guru1.

Итоги

В этом уроке мы рассмотрели многопоточные приложения на Java и узнали, как использовать одно- и многопоточные приложения в Java.

  • Объясните многопоточность в Java: при многопоточности пользователи не блокируются, поскольку потоки независимы и могут выполнять несколько операций одновременно.
  • Различные этапы жизненного цикла потока:
    • Новые
    • Работоспособен
    • Бег
    • Ожидание
    • мертв
  • Мы также узнали о синхронизация между потоками, что помогает приложению работать бесперебойно.
  • Многопоточное программирование на Java упрощает выполнение многих прикладных задач.