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

Содержание:

Anonim

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

В этом уроке мы узнаем:

  • Что такое однопоточный
  • Что такое многопоточность в Java?
  • Жизненный цикл потока в Java
  • Синхронизация потоков Java
  • Пример многопоточности Java

Что такое однопоточный?

Одиночный поток - это, по сути, легкая и самая маленькая единица обработки. Java использует потоки, используя «класс потока».

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

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

Пример одиночной резьбы:

демотест пакета;открытый класс GuruThread{public static void main (String [] args) {System.out.println («Однопоточный»);}}

Преимущества одинарной резьбы:

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

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

МУЛЬТИТРЕЙДИНГ в Java - это процесс одновременного выполнения двух или более потоков с максимальной загрузкой ЦП. Многопоточные приложения выполняют два или более потока одновременно. Следовательно, он также известен как параллелизм в Java. Каждый поток работает параллельно друг другу. Несколько потоков не выделяют отдельную область памяти, поэтому они экономят память. Кроме того, переключение контекста между потоками занимает меньше времени.

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

демотест пакета;открытый класс GuruThread1 реализует Runnable{public static void main (String [] args) {Тема guruThread1 = новая тема ("Guru1");Тема guruThread2 = новая тема ("Guru2");guruThread1.start ();guruThread2.start ();System.out.println ("Имена потоков следующие:");System.out.println (guruThread1.getName ());System.out.println (guruThread2.getName ());}@Overridepublic void run () {}}

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

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

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

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

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

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

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

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

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

демотест пакета;открытый класс thread_example1 реализует Runnable {@Overridepublic void run () {}public static void main (String [] args) {Тема guruthread1 = новая тема ();guruthread1.start ();пытаться {guruthread1.sleep (1000);} catch (InterruptedException e) {// TODO Автоматически сгенерированный блок catche.printStackTrace ();}guruthread1.setPriority (1);int gurupriority = guruthread1.getPriority ();System.out.println (приоритетность);System.out.println («Выполнение потока»);}}

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

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

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

Выход:

5 - это приоритет потока, а выполнение потока - это текст, который является выводом нашего кода.

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

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

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

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

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

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

Это можно записать в следующем виде:

Синхронизированный (объект){// Блок операторов для синхронизации}

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

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

Пример1:

GuruThread1.javaдемотест пакета;открытый класс GuruThread1 реализует Runnable {/ *** @param args* /public static void main (String [] args) {Тема guruThread1 = новая тема ("Guru1");Тема guruThread2 = новая тема ("Guru2");guruThread1.start ();guruThread2.start ();System.out.println ("Имена потоков следующие:");System.out.println (guruThread1.getName ());System.out.println (guruThread2.getName ());}@Overridepublic void run () {}}

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

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

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

Выход:

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

  • Гуру1
  • Гуру2

Пример 2:

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

Также мы берем два класса,

  • Тот, который будет реализовывать исполняемый интерфейс и
  • Еще один, у которого будет основной метод и который будет выполняться соответственно.
демотест пакета;public class GuruThread2 {public static void main (String [] args) {// TODO Заглушка автоматически сгенерированного методаGuruThread3 threadguru1 = новый GuruThread3 ("гуру1");threadguru1.start ();GuruThread3 threadguru2 = новый GuruThread3 ("гуру2");threadguru2.start ();}}class GuruThread3 реализует Runnable {Тема guruthread;личное String guruname;GuruThread3 (имя строки) {guruname = имя;}@Overridepublic void run () {System.out.println ("Поток запущен" + guruname);for (int i = 0; i <4; i ++) {System.out.println (я);System.out.println (имя гуру);пытаться {Thread.sleep (1000);} catch (InterruptedException e) {System.out.println («Поток был прерван»);}}}public void start () {System.out.println («Поток запущен»);if (guruthread == null) {guruthread = новая тема (это, 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 ().

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

Выход :

Имеется два потока, следовательно, мы получаем два раза сообщение «Тема запущена».

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

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

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

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

Резюме :

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

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