Любое приложение может иметь несколько процессов (экземпляров). Каждый из этих процессов может быть назначен как один поток или несколько потоков. В этом руководстве мы увидим, как выполнять несколько задач одновременно, а также узнаем больше о потоках и синхронизации между потоками.
В этом уроке мы узнаем:
- Что такое одиночная тема
- Что такое многопоточность в Java?
- Поток жизненного цикла в Java
- Синхронизация потоков 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. Каждый поток проходит параллельно друг другу. Несколько потоков не выделяют отдельную область памяти, следовательно, они экономят память. Кроме того, переключение контекста между потоками занимает меньше времени.
Пример многопоточности:
- 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
Жизненный цикл потока:
Существуют различные этапы жизненного цикла потока, как показано на диаграмме выше:
- новый
- Runnable
- Бег
- Ожидание
- мертв
- Новое: На этом этапе поток создается с использованием класса «Класс потока». Он остается в этом состоянии до тех пор, пока программа не запустит поток. Это также известно как прирожденная нить.
- Runnable: на этой странице экземпляр потока вызывается методом start. Управление потоком передается планировщику для завершения выполнения. Зависит от планировщика, запускать ли поток.
- Выполняется: когда поток начинает выполняться, состояние изменяется на «запущенное» состояние. Планировщик выбирает один поток из пула потоков, и он начинает выполняться в приложении.
- Ожидание: это состояние, когда поток должен ждать. Поскольку в приложении работает несколько потоков, существует необходимость синхронизации между потоками. Следовательно, один поток должен ждать, пока другой поток не будет выполнен. Следовательно, это состояние называется состоянием ожидания.
- Dead: это состояние, когда поток завершен. Поток находится в рабочем состоянии и, как только он завершил обработку, он находится в «мертвом состоянии».
Некоторые из обычно используемых методов для потоков:
|
|
---|---|
Начните() | Этот метод запускает выполнение потока, а JVM вызывает метод run () в потоке. |
Сон (в миллисекундах) | This method makes the thread sleep hence the thread’s execution will pause for milliseconds provided and after that, again the thread starts executing. This help in synchronization of the threads. |
getName() | It returns the name of the thread. |
setPriority(int newpriority) | It changes the priority of the thread. |
yield () | It causes current thread on halt and other threads to execute. |
Example: In this example we are going to create a thread and explore built-in methods available for threads.
- 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");
- }
- }
Explanation of the code:
- Code Line 2: We are creating a class «thread_Example1» which is implementing the Runnable interface (it should be implemented by any class whose instances are intended to be executed by the thread.)
- Code Line 4: It overrides run method of the runnable interface as it is mandatory to override that method
- Code Line 6: Here we have defined the main method in which we will start the execution of the thread.
- Code Line 7: Here we are creating a new thread name as «guruthread1» by instantiating a new class of thread.
- Code Line 8: we will use «start» method of the thread using «guruthread1» instance. Here the thread will start executing.
- Code Line 10: Here we are using the «sleep» method of the thread using «guruthread1» instance. Hence, the thread will sleep for 1000 milliseconds.
- Code 9-14: Here we have put sleep method in try catch block as there is checked exception which occurs i.e. Interrupted exception.
- Code Line 15: Here we are setting the priority of the thread to 1 from whichever priority it was
- Code Line 16: Here we are getting the priority of the thread using getPriority()
- Code Line 17: Here we are printing the value fetched from getPriority
- Code Line 18: Here we are writing a text that thread is running.
When you execute the above code, you get the following output:
Output:
5 is the Thread priority, and Thread Running is the text which is the output of our code.
Java Thread Synchronization
In multithreading, there is the asynchronous behavior of the programs. If one thread is writing some data and another thread which is reading data at the same time, might create inconsistency in the application.
When there is a need to access the shared resources by two or more threads, then synchronization approach is utilized.
Java предоставила синхронизированные методы для реализации синхронизированного поведения.
При таком подходе, как только поток достигает синхронизированного блока, никакой другой поток не может вызвать этот метод для того же объекта. Все потоки должны ждать, пока этот поток завершит синхронизированный блок и выйдет из этого.
Таким образом, синхронизация помогает в многопоточном приложении. Один поток должен ждать, пока другой поток завершит свое выполнение, только тогда другие потоки будут разрешены для выполнения.
Это можно записать в следующей форме:
- Synchronized(object)
- {
- //Block of statements to be synchronized
- }
Пример многопоточности 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: вывод текста в виде «Имена потоков следующие:»
- Строка кода 14: Получение имени потока 1 с помощью метода getName () класса потока.
- Строка кода 15: Получение имени потока 2 с помощью метода getName () класса потока.
Когда вы выполните приведенный выше код, вы получите следующий вывод:
Вывод:
Имена потоков выводятся здесь как
- Guru1
- Guru2
Пример 2:
В этом примере мы узнаем о переопределении методов 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: мы берем две переменные класса, одна из которых относится к классу типа 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 ().
Когда вы выполните приведенный выше код, вы получите следующий вывод:
Выход :
Таким образом, существует два потока, мы получаем два раза сообщение «Тема начата».
Мы получаем имена потоков по мере их вывода.
Это входит в цикл for, где мы печатаем счетчик и имя потока, а счетчик начинается с 0.
Цикл выполняется три раза, а между тем поток спит 1000 миллисекунд.
Следовательно, сначала мы получаем guru1, затем guru2, затем снова guru2, потому что поток спит здесь в течение 1000 миллисекунд, а затем следующий guru1 и снова guru1, поток спит в течение 1000 миллисекунд, поэтому мы получаем guru2 и затем guru1.
Резюме :
В этом уроке мы увидели многопоточные приложения на Java и то, как использовать одно и несколько потоков.
- В многопоточности пользователи не блокируются, поскольку потоки независимы и могут одновременно выполнять несколько операций.
- Различные этапы жизненного цикла потока,
- новый
- Runnable
- Бег
- Ожидание
- мертв
- Мы также узнали о синхронизации между потоками, которые помогают приложению работать бесперебойно.
- Многопоточность упрощает многие прикладные задачи.