Многопоточность в Java – руководство с примерами
В этом руководстве мы рассмотрим, как выполняется многопоточность в Java , более подробно узнаем о потоках и синхронизации между ними.
Пример одного потока :
Преимущества одного потока :
- При выполнении одного потока снижается нагрузка на приложение;
- Уменьшается стоимость обслуживания приложения.
Что такое многопоточность?
Многопоточность в Java — это выполнение двух или более потоков одновременно для максимального использования центрального процесса.
Многопоточные приложения — это приложения, где параллельно выполняются два или более потоков. Данное понятие известно в Java как многопотоковое выполнение. При этом несколько процессов используют общие ресурсы, такие как центральный процессор, память и т. д.
Все потоки выполняются параллельно друг другу. Для каждого отдельного потока не выделяется память, что приводит к ее экономии. Кроме этого переключение между потоками занимает меньше времени.
Продвинутая Java: Введение в многопоточность в Java
- В задачах на многопоточность Java потоки выполняются независимо друг от друга, поэтому отсутствует блокирование пользователей, и можно выполнять несколько операций одновременно;
- Одни потоки не влияют на другие, когда они наталкиваются на исключения.
Жизненный цикл потока в Java
Жизненный цикл потока :
Стадии жизни потока :
- Новый;
- Готовый к выполнению;
- Выполняемый;
- Ожидающий;
- Остановленный.
- Новый : в этой фазе поток создается с помощью класса Thread . Он остается в этом состоянии, пока программа его не запустит;
- Готовый к выполнению : экземпляр потока вызывается с помощью метода Start . Управление потоком предоставляется планировщику для завершения выполнения. От планировщика зависит то, следует ли запускать поток;
- Выполняемый : с началом выполнения потока его состояние изменяется на « выполняемый ». Планировщик выбирает один поток из пула потоков и начинает его выполнение в приложении;
- Ожидающий : поток ожидает своего выполнения. Поскольку в приложении выполняется сразу несколько потоков, необходимо синхронизировать их. Следовательно, один поток должен ожидать, пока другой поток не будет выполнен. Таким образом, это состояние называется состоянием ожидания;
- Остановленный : выполняемый поток после завершения процесса переходит в состояние « остановленный », известное также как « мертвый ».
Часто используемые методы для управления многопоточностью Java :
Метод | Описание |
start() | Этот метод запускает выполнение потока, а JVM (виртуальная машина Java) вызывает в потоке метод Run (). |
Sleep(int milliseconds) | Делает поток спящим. Его выполнение будет приостановлено на указанное количество миллисекунд, после чего он снова начнет выполняться. Этот метод полезен при синхронизации потоков. |
getName() | Возвращает имя потока. |
setPriority(int newpriority) | Изменяет приоритет потока. |
yield () | Останавливает текущий поток и запускает другие. |
Например : В этом примере создается поток, и применяются перечисленные выше методы.
Живые примеры многопоточности
Объяснение кода
Строка кода 2 : создаем класс » thread_Example1 «, который реализует интерфейс « Runnable » ( готовый к выполнению ). Он должен быть реализован любым классом, экземпляры которого предназначены для выполнения потоком.
Строка 4 : переопределяется метод run для готового к запуску интерфейса, так как он является обязательным при переопределении этого метода.
Строка кода 6 : определяется основной метод, в котором начнется выполнение потока.
Строка кода 7 : создается новое имя потока » guruthread1 «, инициализируя новый класс потока.
Код строка 8 : используется метод » Start » в экземпляре » guruthread1 «. Здесь поток начнет выполняться.
Строка 10 : используется метод « sleep » в экземпляре » guruthread1 «. Поток приостановит свое выполнение на 1000 миллисекунд.
Строки 9—14 : применяется метод « sleep » в блоке « try catch », так как есть проверяемое исключение « Interrupted exception ».
Строка кода 15 : для потока назначается приоритет « 1 », независимо от того, каким приоритет был до этого.
Строка кода 16 : получаем приоритет потока с помощью getPriority() .
Строка кода 17 : значение, извлеченное из getPriority .
Строка кода 18 : пишем текст, что поток выполняется.
Вывод
5 — это приоритет потока, а « Thread Running » — текст, который является выводом нашего кода.
Синхронизация потоков Java
В многопоточности Java присутствует асинхронное поведение. Если один поток записывает некоторые данные, а другой в это время их считывает, в приложении может возникнуть ошибка. Поэтому при необходимости доступа к общим ресурсам двум и более потоками используется синхронизация.
В Java есть свои методы для обеспечения синхронизации. Как только поток достигает синхронизированного блока, другой поток не может вызвать этот метод для того же объекта. Все другие потоки должны ожидать, пока текущий не выйдет из синхронизированного блока.
Таким образом, решается проблема в многопоточных приложениях. Один поток ожидает, пока другой не закончит свое выполнение, и только тогда другим потокам будет разрешено их выполнение.
Это можно написать следующим образом:
Synchronized(object) < //Блок команд для синхронизации >
Пример многопоточности Java
В этом Java многопоточности примере мы задействуем два потока и извлекаем имена потоков.
Пример 1
Объяснение кода
Строка кода 3 : задействуем класс » GuruThread1 «, который реализует интерфейс « Runnable » ( он должен быть реализован любым классом, экземпляры которого предназначены для выполнения потоком ).
Строка 8 : основной метод класса.
Строка 9 : создаем класс Thread , экземпляр с именем » guruThread1 » и поток.
Строка 10 : создаем класс Thread , экземпляр с именем » guruThread2 » и поток.
Строка 11 : запускаем поток guruThread1 .
Строка 12 : запускаем поток guruThread2 .
Строка 13 : выводим текст » Thread names are following: «.
Строка 14 : получаем имя потока 1, используя метод getName() класса thread .
Строка кода 15 : получаем имя потока 2, используя метод getName() класса thread .
Вывод
Имена потоков выводятся как:
Пример 2
Из этого Java многопоточности урока мы узнаем о переопределяющих методах Run () и методе Start () интерфейса runnable . Создадим два потока этого класса и выполним их.
Также мы задействуем два класса:
- Один будет реализовывать интерфейс runnable ;
- Другой — с методом main и будет выполняться.
Объяснение кода
Строка кода 2 : принимаем класс » GuruThread2 «, содержащий метод main .
Строка 4 : принимаем основной метод класса.
Строки 6—7 : создаем экземпляр класса GuruThread3 ( создается в строках внизу ) как » threadguru1 » и запускаем поток.
Строки 8—9 : создаем еще один экземпляр класса GuruThread3 ( создается в строках внизу ) как » threadguru2 » и запускаем поток.
Строка 11 : для многопоточности Java создаем класс » GuruThread3 «, который реализует интерфейс « Runnable ». Он должен быть реализован любым классом, экземпляры которого предназначены для выполнения потоком.
Строки 13—14 : принимаем две переменные класса, из которых одна — потоковый класс, другая — строковый класс.
Строки 15—18 : переопределение конструктора GuruThread3 , который принимает один аргумент как тип String ( являющийся именем потока ). Имя будет присвоено переменной класса guruname и сохраняется имя потока.
Строка 20 : переопределяется метод run() интерфейса runnable .
Строка 21 : выводится имя потока с использованием набора команд println .
Строки 22—31 : используется цикл « for » со счетчиком, инициализированным на « 0 », который не должен быть меньше 4 . Выводится имя потока, а также выполняется приостановка потока на 1000 миллисекунд в блоке try-catch , поскольку метод sleep вызвал проверяемое исключение.
Строка 33 : переопределяется метод start интерфейса runnable .
Строка 35 : выводится текст » Thread started «.
Строки 36—40 : проверяем, содержит ли переменная класса guruthread значение. Если оно равно NULL , создается экземпляр класса thread . После этого запускается поток с использованием класса start() .
При запуске приведенного выше кода получаем следующие выходные данные:
Вывод
Поскольку у нас два потока, то мы дважды получаем сообщение « Thread started ».
Получаем соответствующие имена потоков.
Выполняется цикл, в котором печатается счетчик и имя потока, а счетчик начинается с « 0 ».
Цикл выполняется три раза, а поток приостанавливается на 1000 миллисекунд.
Следовательно, сначала мы получаем guru1 , затем guru2 и снова guru2 , поскольку процесс задерживается на 1000 миллисекунд, а дальше guru1 и снова guru1 . Процесс снова задерживается на 1000 миллисекунд, после чего мы получаем guru2 , а затем guru1 .
Итог
В этом руководстве мы рассмотрели многопоточные приложения в Java , а также то, как использовать один и несколько потоков.
- В многопоточности пользователи не блокируются, поскольку потоки выполняются независимо друг от друга и могут осуществлять несколько операций одновременно.
- Существует несколько стадий жизненного цикла потока:
— Новый;
— Готовый к выполнению;
— Выполняемый;
— Ожидающий;
— Остановленный.
- Мы также узнали о синхронизации между потоками, которая позволяет приложению работать без сбоев;
- Многопоточность упрощает выполнение многих задач приложения.
Источник: www.internet-technologies.ru
Подробно про многопоточность в Java с примерами
Любое приложение может иметь несколько процессов (экземпляров). Каждый из этих процессов может быть назначен как один поток или несколько потоков. В этом руководстве вы увидите, как выполнять несколько задач одновременно, т.е. про многопоточность в Java, а также узнаете о потоках и синхронизации между потоками.
Что такое Single Thread?
Одиночная нить – это в основном легкая и самая маленькая единица обработки. Java использует потоки, используя «класс потоков».
Существует два типа потока – пользовательский поток и поток демона (потоки демона используются, когда мы хотим очистить приложение, и используются в фоновом режиме).
Когда приложение только начинается, создается пользовательский поток. Опубликовать это, мы можем создать много пользовательских потоков.
Пример с одной нитью:
package demotest; public class GuruThread < public static void main(String[] args) < System.out.println(«Single Thread»); >>
Преимущества одной нити:
- Уменьшает накладные расходы в приложении при выполнении одного потока в системе
- Кроме того, это снижает стоимость обслуживания приложения.
Что такое многопоточность в Java?
MULTITHREADING или многопоточность в Java – это процесс одновременного выполнения двух или более потоков с максимальной загрузкой ЦП. Многопоточные приложения выполняют одновременно два или более потоков. Следовательно, он также известен как параллелизм в Java. Каждый поток проходит параллельно друг другу.
Несколько потоков не выделяют отдельную область памяти, следовательно, они экономят память. Кроме того, переключение контекста между потоками занимает меньше времени.
- Пользователи не заблокированы, потому что потоки независимы, и мы можем выполнять несколько операций за раз
- Поскольку такие потоки независимы, другие потоки не будут затронуты, если один поток встретит исключение.
Поток жизненного цикла в Java
Жизненный цикл потока:
Существуют различные этапы жизненного цикла потока, как показано на диаграмме выше:
Некоторые из часто используемых методов для потоков:
Пример: в этом примере мы собираемся создать поток и исследовать встроенные методы, доступные для потоков.
Пояснение к коду:
- Строка кода 2: Мы создаем класс “thread_Example1”, который реализует интерфейс Runnable (он должен быть реализован любым классом, экземпляры которого предназначены для выполнения потоком.)
- Строка кода 4: переопределяет метод run интерфейса runnable, так как этот метод является обязательным
- Строка кода 6: Здесь мы определили основной метод, в котором мы начнем выполнение потока.
- Строка кода 7: Здесь мы создаем новое имя потока как «guruthread1», создавая новый класс потока.
- Строка кода 8: мы будем использовать метод «start» потока, используя экземпляр «guruthread1». Здесь поток начнет выполняться.
- Строка кода 10: Здесь мы используем метод «сна» потока, используя экземпляр «guruthread1». Следовательно, поток будет спать в течение 1000 миллисекунд.
- Код 9-14: Здесь мы поместили спящий метод в блок try catch, поскольку существует проверенное исключение, которое возникает, т.е. исключение Interrupted.
- Строка кода 15: устанавливаем приоритет потока в 1 от того, какой приоритет был
- Строка кода 16: получаем приоритет потока, используя getPriority()
- Строка кода 17: печатаем значение, полученное из getPriority
- Строка кода 18: пишем текст, запущенный потоком.
Когда вы выполните приведенный выше код, вы получите следующий вывод:
5 – это приоритет Thread, а Thread Running – текст, который является выводом нашего кода.
Синхронизация потоков
В многопоточности существует асинхронное поведение программ. Если один поток записывает некоторые данные, а другой поток, который читает данные одновременно, это может привести к несогласованности в приложении.
Когда существует необходимость доступа к совместно используемым ресурсам двумя или более потоками, используется подход синхронизации.
Java предоставила синхронизированные методы для реализации синхронизированного поведения.
При таком подходе, как только поток достигает синхронизированного блока, никакой другой поток не может вызвать этот метод для того же объекта. Все потоки должны ждать, пока этот поток завершит синхронизированный блок и выйдет из этого.
Таким образом, синхронизация помогает в многопоточном приложении. Один поток должен ждать, пока другой поток завершит свое выполнение, только тогда другие потоки будут разрешены для выполнения.
Это можно записать в следующей форме:
Synchronized(object) < //Block of statements to be synchronized >
Пример
В этом примере мы возьмем два потока и извлечем имена потоков.
Пояснение к коду:
- Строка кода 3: Мы взяли класс “GuruThread1”, который реализует Runnable (он должен быть реализован любым классом, экземпляры которого предназначены для выполнения потоком.)
- Строка кода 8: это основной метод класса
- Строка кода 9: Здесь мы создаем экземпляр класса Thread, создаем экземпляр с именем guruThread1 и создаем поток.
- Строка кода 10: Здесь мы создаем экземпляр класса Thread, создаем экземпляр с именем «guruThread2» и создаем поток.
- Строка кода 11: запускаем поток, т.е. guruThread1.
- Строка кода 12: запускаем поток, т.е. guruThread2.
- Строка кода 13: вывод текста в виде «Имена потоков следующие:»
- Строка кода 14: Получение имени потока 1 с помощью метода getName() класса потока.
- Строка кода 15: Получение имени потока 2 с помощью метода getName() класса потока.
Когда вы выполните приведенный выше код, вы получите следующий вывод:
Имена потоков выводятся здесь как
В этом примере мы узнаем о переопределении методов run() и start() исполняемого интерфейса, создадим два потока этого класса и запустим их соответствующим образом.
Кроме того, мы берем два занятия,
- Тот, который будет реализовывать работающий интерфейс и
- Еще один, который будет иметь основной метод и выполнять соответственно.
Пояснение к коду:
- Строка кода 2: Здесь мы возьмем класс GuruThread2, в котором будет основной метод.
- Строка кода 4: Здесь мы берем основной метод класса.
- Строка кода 6-7: здесь мы создаем экземпляр класса GuruThread3 (который создается в нижних строках кода) как «threadguru1», и мы запускаем поток.
- Строка кода 8-9: Здесь мы создаем еще один экземпляр класса GuruThread3 (который создается в нижних строках кода) как «threadguru2», и мы запускаем поток.
- Строка кода 11: Здесь мы создаем класс “GuruThread3”, который реализует работающий интерфейс (он должен быть реализован любым классом, экземпляры которого предназначены для выполнения потоком.)
- Строка кода 13-14: мы берем две переменные класса, одна из которых относится к классу типа thread, а другая – к классу string.
- Строка кода 15-18: мы переопределяем конструктор GuruThread3, который принимает один аргумент в качестве строкового типа (который является именем потока), который присваивается переменной класса guruname и, следовательно, сохраняется имя потока.
- Строка кода 20: Здесь мы переопределяем метод run() интерфейса runnable.
- Строка кода 21: Мы выводим имя потока, используя оператор println.
- Строка кода 22-31: Здесь мы используем цикл for со счетчиком, инициализированным в 0, и он должен быть не меньше 4 (мы можем взять любое число, поэтому здесь цикл будет выполняться 4 раза) и увеличивать счетчик. Мы печатаем имя потока, а также переводим его в спящий режим на 1000 миллисекунд в блоке try-catch, поскольку спящий метод вызывает проверенное исключение.
- Строка кода 33: Здесь мы переопределяем метод запуска работающего интерфейса.
- Строка кода 35: Мы выводим текст «Тема начата».
- Строка кода 36-40: здесь мы берем условие if, чтобы проверить, имеет ли значение переменной класса guruthread значение в нем или нет. Если его значение равно NULL, то мы создаем экземпляр, используя класс потока, который принимает имя в качестве параметра (значение, которое было назначено в конструкторе). После чего поток запускается методом start().
Когда вы выполните приведенный выше код, вы получите следующий вывод:
Таким образом, существует два потока, и мы получаем два раза сообщение “Тема начата”.