Любое приложение может иметь несколько процессов (экземпляров). Каждый из этих процессов может быть назначен как один поток или несколько потоков. В этом руководстве вы увидите, как выполнять несколько задач одновременно, т.е. про многопоточность в 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
Жизненный цикл потока:

Существуют различные этапы жизненного цикла потока, как показано на диаграмме выше:
Некоторые из часто используемых методов для потоков:
Пример: в этом примере мы собираемся создать поток и исследовать встроенные методы, доступные для потоков.
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: переопределяет метод 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
}
Пример
В этом примере мы возьмем два потока и извлечем имена потоков.
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.
