Многопоточное программирование
Большинство языков программирования поддерживают такую важную функциональность как многопоточность, и Java в этом плане не исключение. При помощи многопоточности мы можем выделить в приложении несколько потоков, которые будут выполнять различные задачи одновременно. Если у нас, допустим, графическое приложение, которое посылает запрос к какому-нибудь серверу или считывает и обрабатывает огромный файл, то без многопоточности у нас бы блокировался графический интерфейс на время выполнения задачи. А благодаря потокам мы можем выделить отправку запроса или любую другую задачу, которая может долго обрабатываться, в отдельный поток. Поэтому большинство реальных приложений, которые многим из нас приходится использовать, практически не мыслимы без многопоточности.
Класс Thread
В Java функциональность отдельного потока заключается в классе Thread . И чтобы создать новый поток, нам надо создать объект этого класса. Но все потоки не создаются сами по себе. Когда запускается программа, начинает работать главный поток этой программы. От этого главного потока порождаются все остальные дочерние потоки.
С помощью статического метода Thread.currentThread() мы можем получить текущий поток выполнения:
public static void main(String[] args) < Thread t = Thread.currentThread(); // получаем главный поток System.out.println(t.getName()); // main >
По умолчанию именем главного потока будет main .
Для управления потоком класс Thread предоставляет еще ряд методов. Наиболее используемые из них:
- getName() : возвращает имя потока
- setName(String name) : устанавливает имя потока
- getPriority() : возвращает приоритет потока
- setPriority(int proirity) : устанавливает приоритет потока. Приоритет является одним из ключевых факторов для выбора системой потока из кучи потоков для выполнения. В этот метод в качестве параметра передается числовое значение приоритета — от 1 до 10. По умолчанию главному потоку выставляется средний приоритет — 5.
- isAlive() : возвращает true, если поток активен
- isInterrupted() : возвращает true, если поток был прерван
- join() : ожидает завершение потока
- run() : определяет точку входа в поток
- sleep() : приостанавливает поток на заданное количество миллисекунд
- start() : запускает поток, вызывая его метод run()
Мы можем вывести всю информацию о потоке:
public static void main(String[] args) < Thread t = Thread.currentThread(); // получаем главный поток System.out.println(t); // main >
Thread[main,5,main]
Первое main будет представлять имя потока (что можно получить через t.getName() ), второе значение 5 предоставляет приоритет потока (также можно получить через t.getPriority() ), и последнее main представляет имя группы потоков, к которому относится текущий — по умолчанию также main (также можно получить через t.getThreadGroup().getName() )
Недостатки при использовании потоков
Далее мы рассмотрим, как создавать и использовать потоки. Это довольно легко. Однако при создании многопоточного приложения нам следует учитывать ряд обстоятельств, которые негативно могут сказаться на работе приложения.
На некоторых платформах запуск новых потоков может замедлить работу приложения. Что может иметь большое значение, если нам критичная производительность приложения.
Для каждого потока создается свой собственный стек в памяти, куда помещаются все локальные переменные и ряд других данных, связанных с выполнением потока. Соответственно, чем больше потоков создается, тем больше памяти используется. При этом надо помнить, в любой системе размеры используемой памяти ограничены. Кроме того, во многих системах может быть ограничение на количество потоков. Но даже если такого ограничения нет, то в любом случае имеется естественное ограничение в виде максимальной скорости процессора.
Многопоточность
Многопоточность — способность платформы (например виртуальной машины, операционной системы и т.д.) или приложения выполнять одновременно, то есть без предписанного порядка во времени, несколько параллельных задач — потоков.
Освойте профессию «Java-разработчик»
Такое разделение позволяет эффективнее использовать ресурсы вычислительной машины, обеспечивает комфортный пользовательский опыт.
Профессия / 14 месяцев
Java-разработчик
Освойте востребованный язык

Многопоточность в Java
Реализация многопоточности в Java предусматривает параллельное выполнение нескольких частей программы (потоков). Каждый работающий в операционной системе процесс может состоять из нескольких потоков.

Потоки в Java можно создавать двумя способами.
Создание потоков с помощью расширения класса Thread
Метод заключается в создании класса, который расширяет java.lang.Thread класс. Он переопределяет метод run(), доступный в классе Thread. Поток начинает жизненный цикл внутри метода run(). Мы создаем объект нашего нового класса и вызываем метод start(), чтобы начать выполнение потока. Метод start() вызывает метод run() на объекте Thread:
class MultithreadingDemo extends Thread public void run()
try // Вывод на экран номера исполняемого потока
System.out.println(
«Сейчас выполняется » + Thread.currentThread().getId()
+ » поток»);
>
catch (Exception e) // Обработка исключений
System.out.println(«Поймано исключение»);
>
>
>
// Главный класс
public class Multithread public static void main(String[] args)
int n = 8; // Число потоков
for (int i = 0; i < n; i++) MultithreadingDemo object
= new MultithreadingDemo();
object.start();
>
>
>

Станьте Java-разработчиком
и создавайте сложные сервисы
на востребованном языке
В результате выполнения программа будет выводить на экран номера выполняемых потоков в формате «Сейчас выполняется n-й поток».

Создание потоков с использованием интерфейса Runnable
При использовании этого подхода нужно создать новый класс, реализующий интерфейс java.lang.Runnable и переопределяющий метод run(). После этого появляется экземпляр объекта Thread и вызывается метод start(). Код программы выглядит так:
class MultithreadingDemo implements Runnable public void run()
try // Вывод на экран номера исполняемого потока
System.out.println(
«Сейчас выполняется » + Thread.currentThread().getId()
+ » поток»);
>
catch (Exception e) // Обработка исключений
System.out.println(«Поймано исключение»);
>
>
>
// Главный класс
class Multithread public static void main(String[] args)
int n = 8; // Число потоков
for (int i = 0; i < n; i++) Thread object
= new Thread(new MultithreadingDemo());
object.start();
>
>
>
Формат вывода результатов идентичен первому способу.
Сравнение способов создания потоков
В случае расширения Thread созданный нами класс уже не сможет расширить какой-то другой класс, поскольку Java не поддерживает множественное наследование.
Второй способ лишен этого недостатка: при использовании интерфейса Runnable новый класс может расширять другие основные классы. Расширение класса Thread позволяет новому классу получать базовую функциональность, поскольку Thread предоставляет ряд полезных встроенных методов, например yield() и interrupt(). Такая опция недоступна при использовании интерфейса Runnable, однако Runnable может создать объект, который будет общим для нескольких потоков.
Умение писать код, который выполняет многопоточную обработку данных, позволяет разработчику создавать высокоэффективные приложения, оптимально использующие современные многоядерные процессоры.
Java-разработчик
Java уже 20 лет в мировом топе языков программирования. На нем создают сложные финансовые сервисы, стриминги и маркетплейсы. Освойте технологии, которые нужны для backend-разработки, за 14 месяцев.

Статьи по теме:
- SQLite
- Coroutine
- Асинхронное программирование
36. Java – Многопоточность
Java является многопоточным языком программирования, а это значит, что с помощью него мы можем разрабатывать многопоточные программы. Многопоточная программа состоит из двух или более частей, которые могут выполняться одновременно, и каждая часть может одновременно обрабатывать разные задачи, оптимально используя доступные ресурсы, особенно если компьютер имеет несколько ЦП.
По определению, многозадачность – это совместное использование несколькими процессами общих ресурсов обработки, таких как ЦП. Многопоточность расширяет идею многозадачности на приложения, в которых вы можете разделить определенные операции в рамках одного приложения на отдельные потоки. Каждый из потоков может работать параллельно. ОС распределяет время обработки не только между различными приложениями, но и между каждым потоком в приложении.
Многопоточность позволяет создавать код таким образом, чтобы несколько действий могли выполняться одновременно в одной программе.
Жизненный цикл потока
В своем жизненном цикле поток проходит различные стадии. Например, поток создается, запускается, выполняется и затем останавливается. На следующей схеме показан полный жизненный цикл потока.

Ниже приведены этапы жизненного цикла:
- Новый− Новый поток начинает свой жизненный цикл в состоянии нового. Он сохраняет это состояние до тех пор, пока программа не запустит поток. Его также называют созданным потоком.
- Запущенный − После запуска нового потока он становится запущенным. Считается, что поток в этом состоянии выполняет свою задачу.
- Ожидающий − Иногда поток переходит в состояние ожидания, т.е. поток ожидает, пока другой поток выполнит задачу. Поток переходит обратно в запущенное состояние только после того, когда другой поток сигнализирует ожидающему потоку продолжить выполнение.
- Ожидающий с ограничением по времени − Запущенный поток может войти в состояние ожидания с ограничением по времени в течение определенного интервала времени. Поток в этом состоянии переходит обратно в запущенное состояние, когда истекает этот временной интервал или когда происходит событие, которого он ожидает.
- Остановленный− Запущенный поток переходит в остановленное состояние, когда он завершает свою задачу или иным образом завершается.
Приоритеты потоков
Каждый поток Java имеет приоритет, который помогает операционной системе определять порядок, в котором планируются потоки.
Приоритеты потоков Java находятся в диапазоне от MIN_PRIORITY (константа 1) до MAX_PRIORITY (константа 10). По умолчанию каждому потоку устанавливается приоритет NORM_PRIORITY (константа 5).
Потоки с более высоким приоритетом более важны для программы, и в первую очередь им должно выделяться процессорное время. Однако приоритеты потоков не могут гарантировать порядок, в котором выполняются потоки, и очень сильно зависят от платформы.
Создание потока путем реализации интерфейса Runnable
Если ваш класс предназначен для выполнения как поток, вы можете добиться этого, реализовав интерфейс Runnable. Вам нужно будет выполнить три основных шага:
Шаг 1
В качестве первого шага вам необходимо реализовать метод run(), предоставляемый интерфейсом Runnable. Этот метод обеспечивает точку входа для потока, и вы поместите в него полный код, реализующий функциональность приложения. Ниже приводится простой синтаксис метода run().
public void run( )
Шаг 2
На втором шаге вы создадите экземпляр объекта Thread, используя следующий конструктор:
Thread(Runnable threadObj, String threadName);
Где threadObj — это экземпляр класса, который реализует интерфейс Runnable, а threadName — это имя, данное новому потоку.
Шаг 3
После создания объекта Thread вы можете запустить его, вызвав метод start(), который выполняет вызов метода run(). Ниже приводится простой синтаксис метода start().
void start();
Пример
Вот пример, который создает новый поток и запускает его:
class RunnableDemo implements Runnable < private Thread t; private String threadName; RunnableDemo( String name) < threadName = name; System.out.println("Создание " + threadName ); >public void run() < System.out.println("Выполнение " + threadName ); try < for(int i = 4; i >0; i--) < System.out.println("Поток: " + threadName + ", " + i); // Пусть поток немного подождет. Thread.sleep(50); >> catch (InterruptedException e) < System.out.println("Поток " + threadName + " прерван."); >System.out.println("Поток " + threadName + " завершается."); > public void start () < System.out.println("Запуск " + threadName ); if (t == null) < t = new Thread (this, threadName); t.start (); >> > public class TestThread < public static void main(String args[]) < RunnableDemo R1 = new RunnableDemo( "Поток-1"); R1.start(); RunnableDemo R2 = new RunnableDemo( " Поток-2"); R2.start(); >>
Это даст следующий результат:
Создание Поток-1 Запуск Поток-1 Создание Поток-2 Запуск Поток-2 Выполнение Поток-1 Поток: Поток-1, 4 Выполнение Поток-2 Поток: Поток-2, 4 Поток: Поток-1, 3 Поток: Поток-2, 3 Поток: Поток-1, 2 Поток: Поток-2, 2 Поток: Поток-1, 1 Поток: Поток-2, 1 Поток Поток-1 завершается. Поток Поток-2 завершается.
Создание потока путем расширения класса Thread
Второй способ создания потока в Java заключается в создании нового класса, расширяющего класс Thread посредством следующих двух простых шагов. Этот подход обеспечивает большую гибкость в обработке нескольких потоков, созданных с использованием доступных методов в классе Thread.
Шаг 1
Вам нужно будет переопределить метод run(), доступный в классе Thread. Этот метод обеспечивает точку входа для потока, и вы поместите в него полный код, реализующий функциональность приложения. Ниже приводится простой синтаксис метода run().
public void run( )
Шаг 2
После создания объекта Thread вы можете запустить его, вызвав метод start(), который выполняет вызов метода run(). Ниже приведен простой синтаксис метода start():
void start( );
Пример
Вот предыдущая программа, переписанная для расширения потока:
class ThreadDemo extends Thread < private Thread t; private String threadName; ThreadDemo( String name) < threadName = name; System.out.println("Создание " + threadName ); >public void run() < System.out.println("Выполнение " + threadName ); try < for(int i = 4; i >0; i--) < System.out.println("Поток: " + threadName + ", " + i); // Пусть поток немного подождет. Thread.sleep(50); >> catch (InterruptedException e) < System.out.println("Поток " + threadName + " прерывается."); >System.out.println("Поток " + threadName + " завершается."); > public void start () < System.out.println("Запуск " + threadName ); if (t == null) < t = new Thread (this, threadName); t.start (); >> > public class TestThread < public static void main(String args[]) < ThreadDemo T1 = new ThreadDemo( "Поток-1"); T1.start(); ThreadDemo T2 = new ThreadDemo( "Поток-2"); T2.start(); >>
Это даст следующий результат:
Создание Поток-1 Запуск Поток-1 Создание Поток-2 Запуск Поток-2 Выполнение Поток-1 Поток: Поток-1, 4 Выполнение Поток-2 Поток: Поток-2, 4 Поток: Поток-1, 3 Поток: Поток-2, 3 Поток: Поток-1, 2 Поток: Поток-2, 2 Поток: Поток-1, 1 Поток: Поток-2, 1 Поток Поток-1 завершается. Поток Поток-2 завершается.
Методы потока
Ниже приводится список важных методов, доступных в классе Thread в Java.
Метод и описание
public void start()
Запускает поток по отдельному пути выполнения, затем вызывает метод run() для этого объекта Thread.
public void run()
Если этот объект Thread был создан с использованием отдельной цели Runnable, метод run() вызывается для этого объекта Runnable.
public final void setName(String name)
Изменяет имя объекта Thread. Также существует метод getName() для получения имени.
public final void setPriority(int priority)
Устанавливает приоритет этого объекта Thread. Возможные значения от 1 до 10.
public final void setDaemon(boolean on)
Параметр true обозначает этот поток как вспомогательный поток.
public final void join(long millisec)
Текущий поток вызывает этот метод во втором потоке, в результате чего текущий поток блокируется до тех пор, пока второй поток не завершится или не пройдет заданное количество миллисекунд.
public void interrupt()
Прерывает этот поток, заставляя его продолжить выполнение, если он был заблокирован по какой-либо причине.
public final boolean isAlive()
Возвращает true, если поток активен, то есть в любое время после запуска потока, но до его завершения.
Предыдущие методы вызываются для конкретного объекта Thread. Следующие методы в классе Thread являются статическими. Вызов одного из статических методов выполняет операцию в текущем запущенном потоке.
Метод и описание
public static void yield()
Заставляет текущий запущенный поток уступить место любым другим потокам с тем же приоритетом, которые ожидают планирования.
public static void sleep(long millisec)
Блокирует текущий запущенный поток по крайней мере на указанное количество миллисекунд.
public static boolean holdsLock(Object x)
Возвращает true, если текущий поток удерживает блокировку данного объекта.
public static Thread currentThread()
Возвращает ссылку на текущий запущенный поток, который вызывает этот метод.
public static void dumpStack()
Выводит отслеживание стека для текущего запущенного потока, что полезно при отладке многопоточного приложения.
Пример
Следующая программа ThreadClassDemo демонстрирует некоторые из этих методов класса Thread. Рассмотрим класс DisplayMessage, который реализует Runnable:
// Название файла: DisplayMessage.java // Создаем поток для реализации Runnable public class DisplayMessage implements Runnable < private String message; public DisplayMessage(String message) < this.message = message; >public void run() < while(true) < System.out.println(message); >> >
Ниже приведен еще один класс, который расширяет класс Thread.
// Название файла: GuessANumber.java // Создаем поток для расширения Thread public class GuessANumber extends Thread < private int number; public GuessANumber(int number) < this.number = number; >public void run() < int counter = 0; int guess = 0; do < guess = (int) (Math.random() * 100 + 1); System.out.println(this.getName() + " угадываний " + guess); counter++; >while(guess != number); System.out.println("** Верно!" + this.getName() + "за" + counter + "угадываний.**"); > >
Ниже приведена основная программа, в которой используются выше определенные классы.
// Название файла: ThreadClassDemo.java public class ThreadClassDemo < public static void main(String [] args) < Runnable hello = new DisplayMessage("Привет"); Thread thread1 = new Thread(hello); thread1.setDaemon(true); thread1.setName("привет"); System.out.println("Запуск потока привет. "); thread1.start(); Runnable bye = new DisplayMessage("Пока"); Thread thread2 = new Thread(bye); thread2.setPriority(Thread.MIN_PRIORITY); thread2.setDaemon(true); System.out.println("Запуск потока пока. "); thread2.start(); System.out.println("Запуск потока3. "); Thread thread3 = new GuessANumber(27); thread3.start(); try < thread3.join(); >catch (InterruptedException e) < System.out.println("Поток прерван."); >System.out.println("Запуск потока4. "); Thread thread4 = new GuessANumber(75); thread4.start(); System.out.println("main() завершается. "); > >
Это даст следующий результат. Вы можете пробовать этот пример снова и снова, и каждый раз вы будете получать разные результаты.
Вывод Запуск потока привет. Запуск потока пока. Привет Привет Привет Привет Привет Привет Пока Пока Пока Пока Пока .
Основные концепции многопоточности Java
При выполнении многопоточного программирования на Java вы столкнетесь со следующими вопросами:
- Что такое синхронизация потоков?
- Обработка межпоточной связи
- Обработка взаимной блокировки потоков
- Основные операции с потоками
Оглавление
- 1. Java – Самоучитель для начинающих
- 2. Java – Обзор языка
- 3. Java – Установка и настройка
- 4. Java – Синтаксис
- 5. Java – Классы и объекты
- 6. Java – Конструкторы
- 7. Java – Типы данных и литералы
- 8. Java – Типы переменных
- 9. Java – Модификаторы
- 10. Java – Операторы
- 11. Java – Циклы и операторы цикла
- 11.1. Java – Цикл while
- 11.2. Java – Цикл for
- 11.3. Java – Улучшенный цикл for
- 11.4. Java – Цикл do..while
- 11.5. Java – Оператор break
- 11.6. Java – Оператор continue
- 12. Java – Операторы принятия решений
- 12.1. Java – Оператор if
- 12.2. Java – Оператор if..else
- 12.3. Java – Вложенный оператор if
- 12.4. Java – Оператор switch..case
- 12.5. Java – Условный оператор (? 🙂
- 13. Java – Числа
- 13.1. Java – Методы byteValue(), shortValue(), intValue(), longValue(), floatValue(), doubleValue()
- 13.2. Java – Метод compareTo()
- 13.3. Java – Метод equals()
- 13.4. Java – Метод valueOf()
- 13.5. Java – Метод toString()
- 13.6. Java – Метод parseInt()
- 13.7. Java – Метод Math.abs()
- 13.8. Java – Метод Math.ceil()
- 13.9. Java – Метод Math.floor()
- 13.10. Java – Метод Math.rint()
- 13.11. Java – Метод Math.round()
- 13.12. Java – Метод Math.min()
- 13.13. Java – Метод Math.max()
- 13.14. Java – Метод Math.exp()
- 13.15. Java – Метод Math.log()
- 13.16. Java – Метод Math.pow()
- 13.17. Java – Метод Math.sqrt()
- 13.18. Java – Метод Math.sin()
- 13.19. Java – Метод Math.cos()
- 13.20. Java – Метод Math.tan()
- 13.21. Java – Метод Math.asin()
- 13.22. Java – Метод Math.acos()
- 13.23. Java – Метод Math.atan()
- 13.24. Java – Метод Math.atan2()
- 13.25. Java – Метод Math.toDegrees()
- 13.26. Java – Метод Math.toRadians()
- 13.27. Java – Метод Math.random()
- 14. Java – Символы
- 14.1. Java – Метод Character.isLetter()
- 14.2. Java – Метод Character.isDigit()
- 14.3. Java – Метод Character.isWhitespace()
- 14.4. Java – Метод Character.isUpperCase()
- 14.5. Java – Метод Character.isLowerCase()
- 14.6. Java – Метод Character.toUpperCase()
- 14.7. Java – Метод Character.toLowerCase()
- 14.8. Java – Метод Character.toString()
- 15. Java – Строки
- 15.1. Java – Метод charAt()
- 15.2. Java – Метод compareTo()
- 15.3. Java – Метод compareToIgnoreCase()
- 15.4. Java – Метод concat()
- 15.5. Java – Метод contentEquals()
- 15.6. Java – Метод copyValueOf()
- 15.7. Java – Метод endsWith()
- 15.8. Java – Метод equals()
- 15.9. Java – Метод equalsIgnoreCase()
- 15.10. Java – Метод getBytes()
- 15.11. Java – Метод getChars()
- 15.12. Java – Метод hashCode()
- 15.13. Java – Метод indexOf()
- 15.14. Java – Метод intern()
- 15.15. Java – Метод lastIndexOf()
- 15.16. Java – Метод length()
- 15.17. Java – Метод matches()
- 15.18. Java – Метод regionMatches()
- 15.19. Java – Метод replace()
- 15.20. Java – Метод replaceAll()
- 15.21. Java – Метод replaceFirst()
- 15.22. Java – Метод split()
- 15.23. Java – Метод startsWith()
- 15.24. Java – Метод subSequence()
- 15.25. Java – Метод substring()
- 15.26. Java – Метод toCharArray()
- 15.27. Java – Метод toLowerCase()
- 15.28. Java – Метод toString()
- 15.29. Java – Метод toUpperCase()
- 15.30. Java – Метод trim()
- 15.31. Java – Метод valueOf()
- 15.32. Java – Классы StringBuilder и StringBuffer
- 15.32.1. Java – Метод append()
- 15.32.2. Java – Метод reverse()
- 15.32.3. Java – Метод delete()
- 15.32.4. Java – Метод insert()
- 15.32.5. Java – Метод replace()
- 16. Java – Массивы
- 17. Java – Дата и время
- 18. Java – Регулярные выражения
- 19. Java – Методы
- 20. Java – Потоки ввода/вывода, файлы и каталоги
- 20.1. Java – Класс ByteArrayInputStream
- 20.2. Java – Класс DataInputStream
- 20.3. Java – Класс ByteArrayOutputStream
- 20.4. Java – Класс DataOutputStream
- 20.5. Java – Класс File
- 20.6. Java – Класс FileReader
- 20.7. Java – Класс FileWriter
- 21. Java – Исключения
- 21.1. Java – Встроенные исключения
- 22. Java – Вложенные и внутренние классы
- 23. Java – Наследование
- 24. Java – Переопределение
- 25. Java – Полиморфизм
- 26. Java – Абстракция
- 27. Java – Инкапсуляция
- 28. Java – Интерфейсы
- 29. Java – Пакеты
- 30. Java – Структуры данных
- 30.1. Java – Интерфейс Enumeration
- 30.2. Java – Класс BitSet
- 30.3. Java – Класс Vector
- 30.4. Java – Класс Stack
- 30.5. Java – Класс Dictionary
- 30.6. Java – Класс Hashtable
- 30.7. Java – Класс Properties
- 31. Java – Коллекции
- 31.1. Java – Интерфейс Collection
- 31.2. Java – Интерфейс List
- 31.3. Java – Интерфейс Set
- 31.4. Java – Интерфейс SortedSet
- 31.5. Java – Интерфейс Map
- 31.6. Java – Интерфейс Map.Entry
- 31.7. Java – Интерфейс SortedMap
- 31.8. Java – Класс LinkedList
- 31.9. Java – Класс ArrayList
- 31.10. Java – Класс HashSet
- 31.11. Java – Класс LinkedHashSet
- 31.12. Java – Класс TreeSet
- 31.13. Java – Класс HashMap
- 31.14. Java – Класс TreeMap
- 31.15. Java – Класс WeakHashMap
- 31.16. Java – Класс LinkedHashMap
- 31.17. Java – Класс IdentityHashMap
- 31.18. Java – Алгоритмы Collection
- 31.19. Java – Iterator и ListIterator
- 31.20. Java – Comparator
- 32. Java – Дженерики
- 33. Java – Сериализация
- 34. Java – Сеть
- 34.1. Java – Обработка URL
- 35. Java – Отправка Email
- 36. Java – Многопоточность
- 36.1. Java – Синхронизация потоков
- 36.2. Java – Межпоточная связь
- 36.3. Java – Взаимная блокировка потоков
- 36.4. Java – Управление потоками
- 37. Java – Основы работы с апплетами
- 38. Java – Javadoc
Что такое многопоточность в java

В приведенном выше фрагменте кода мы создаем класс Car , который наследует класс Thread и переопределяет его метод run(). Внутри метода run() мы просто выводим модель автомобиля и имя выполняемого потока.
Thread.sleep(1000) — останавливает этот поток на заданный период времени (в миллисекундах). В main-методе мы создаем два экземпляра (ferrari, bmw) класса Car и вызываем метод start() для каждого из них. Затем выводим какое-нибудь сообщение. По умолчанию всякий раз, когда запускается любая Java-программа, она выполняется основным потоком. Запуск этой программы дает следующий вывод.

Как видим из вывода, вывод сообщения, которое написали в методе main — последняя команда в программе, но она выводится в консоль первой и не ждёт выполнения вызовов методов — ferrari.start() и bmw.start() . Это и есть магия многопоточности. Сколько бы времени ни потребовалось для выполнения методов — ferrari.start() и bmw.start(), поток main дальше выполняется и не ждёт их завершения.
Создание потоков путем реализации интерфейса Runnable

Принимая во внимание два способа создания потоков, второй считается более предпочтительным, поскольку множественное наследование запрещено в Java , и, унаследовав класс Thread , мы не сможем унаследовать какой-либо другой класс. Однако, реализовав интерфейс Runnable , мы сможем унаследовать другой класс. Это небольшое преимущество второго способа.
☕ Интересные задачи по Java для практики можно найти на нашем телеграм-канале «Библиотека задач по Java»
Что произойдет, если мы вызовем метод run() класса Thread?

При вызове метода run() , программа выполняется последовательно, в том порядке, в котором мы написали. Вызов метода run() не создает новый поток, он ведет себя как обычный метод в Java .
Процессы и потоки
Термины «процесс» и «поток» повсеместно используются, когда речь идет о многопоточности. Давайте подробно рассмотрим эти две концепции.
Процесс — это исполняемая программа, или, другими словами, просто запущенная программа
- когда вы запускаете какое-нибудь приложение или веб-браузер, запускаются разные процессы;
- операционная система назначает каждому процессу отдельные регистры, программные счетчики, память кучи и стека;
- процессы полностью независимы и не имеют общей памяти или данных;
- переключение контекста и связь между процессами занимают больше времени, поскольку они тяжелые, создание новых процессов требует больше ресурсов по сравнению с потоками.
Поток — это легкий процесс
- это единица выполнения внутри данного процесса, один процесс может иметь один и более потоков;
- каждый поток с данным процессом разделяет память и ресурсы, поэтому программисты имеют дело с параллелизмом и синхронизацией;
- создание нового потока требует меньше ресурсов, чем новый процесс, коммуникация между потоками и переключением контекста быстрее по сравнению с переключением контекста и коммуникации процессов.
Многопоточность и алгоритм разделения времени
Представьте, что у вас есть устройство с одним ядром и запущенное приложение, которое требует k потоков ( k > 1 ). В этом случае одному процессору приходится обрабатывать k потоков. Как процессор обрабатывает k – потоков? Вот где алгоритм time-slicing делает всю магию.
При наличии нескольких потоков время обработки одноядерного процессора распределяется между процессами и потоками. Все потоки планируются процессором случайным образом c помощью thread scheduler , и каждый поток получает минимальное количество времени для выполнения. Как только выделенное время истекает, другой поток получает свою часть процессорного времени и начинает свою часть выполнения. Этот процесс выделения процессором времени потокам продолжается до тех пор, пока все потоки не завершат свое выполнение. Это называется алгоритмом квантования времени (time slicing algorithm). Под капотом потоки выполняются последовательно, однако они выполняются настолько быстро, что у нас возникает ощущение параллельного выполнения. Это называется simulated or fake concurrency. .
Это было краткое введение в многопоточность в Java . В следующих постах мы рассмотрим жизненный цикл потоков, разницу между процессом и потоком, разницу между многопоточностью и параллелизмом и т. д. Продолжение следует.
Материалы по теме
- ☕ Изучение Java с нуля: что должен знать junior?
- ☕ Топ-10 книг по Java, вышедших за последние два года
