Включите исполнение JavaScript в браузере, чтобы запустить приложение.
23 авг 2024

Потоки в Java: что это такое и как они работают

Потоки в Java – способ реализации многозадачности и повышения производительности программ. Узнайте подробнее в статье на нашем сайте.

1.      Что такое потоки в Java

2.      Потоки и многозадачность

3.      Создание и управление потоками

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

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

6.      Преимущества и недостатки многопоточности

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

В этой статье расскажем, что такое потоки в Джава и как их создавать, рассмотрим их жизненный цикл, синхронизацию и, конечно, раскроем преимущества и недостатки многопоточности (multithreading).

Что такое потоки в Java

Поток (ветвь исполнения) в Java — это отдельная последовательность выполнения команд в коде. Каждый из них работает независимо и параллельно с другими. Простой пример: у Маши есть два дела — сделать маме чай и почистить зубы. Она может делать это последовательно, но потратит много времени. Поэтому, пока кипит чайник, она почистит зубы — и распределит время эффективно. Потоки Java работают подобным образом, только могут выполнять куда больше команд одновременно. Современное программирование трудно представить без них. 

Потоки и многозадачность

Многозадачность — это способность программ выполнять несколько операций одновременно. Благодаря ей приложения могут выполнять фоновые операции, реагировать на действия пользователей без задержек. В Джава многозадачность реализуется путем создания ветвей исполнения.

Создание и управление потоками

В Джава есть несколько способов создания многопоточного кода, но в этой статье мы рассмотрим два основных: использование class «Thread» и интерфейса «Runnable».

Class «Thread»

Суть способа: необходимо создать класс, который наследуется от Thread. Затем нужно переопределить run(): вставить туда код, который должен быть выполнен в данной ветви исполнения. Далее — объявить класс Main и определить его метод main(). 

Пример:

// Объявляем класс и переопределяем run()
class NewThread extends Thread {
	public void run() {
  	System.out.println("Привет, мир!");
	}
}
 // Объявляем класс Main, определяем main(). Cоздаем экземпляр NewThread и вызываем start()
public class Main {
	public static void main(String[] args) {
  	NewThread thread = new NewThread();
  	thread.start();
	}
}
java

Интерфейс «Runnable»

Суть: необходимо создать класс, реализующий интерфейс «Runnable», реализовать в нем run(). run() должен содержать в себе код, который будет исполняться вместе с основной ветвью программы. Затем нужно объявить класс Main и main().

Пример:

 // Объявляем класс NewRunnable и реализуем run()
class NewRunnable implements Runnable {
	public void run() {
  	System.out.println("Привет, мир!");
	}
}
 // Объявляем класс Main, определяем main(), создаем экземпляр NewRunnable, вызываем start()
public class Main {
	public static void main(String[] args) {
  	Thread thread = new Thread(new NewRunnable());
    thread.start();
	}
}
java

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

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

Для более полного погружения в тему стоит рассмотреть жизненный цикл потока, а точнее — состояния внутри него. 

  1. New — создан, не запущен: для запуска он ждет команду start().
  2. Runnable — запущен / готов к исполнению / ждет выделения ресурсов (CPU time).
  3. Blocked — заблокирован, ожидает освобождения ресурса, необходимого для продолжения выполнения операции.
  4. Waiting и Timed_waiting — ожидание определенного сигнала для продолжения работы. Waiting значит ожидание без временных ограничений, Timed_waiting — с ними.
  5. Terminated — конечное состояние, выполнение операций завершено.

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

Поговорим и о таком важном аспекте, как синхронизация. Она необходима для предупреждения конфликтов при единовременном обращении к общим ресурсам. Под ресурсами мы в том числе понимаем данные, файлы, сетевые и системные ресурсы. Конфликты приводят к несогласованности данных, недетерминированному поведению и мертвым блокировкам.

Рассмотрим самый популярный в современном программировании механизм управления синхронизацией в Java — ключевое слово synchronized. Объявление сущности синхронной делает возможным использовать ее в данный момент только одной ветвью.

Пример объявления метода синхронным (пример бессмысленный, нужен для демонстрации синтаксиса):

class Counter {
	private int count = 0;
// Увеличиваем значение счетчика на 1
	public synchronized int increment() {
  		count += 1;
	}
// Получаем текущее значение count
public synchronized int getCount() {
       		 return count;
   	 }
java

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

Подведем небольшой итог данной статьи и рассмотрим плюсы и минусы мультипоточности, или multithreading, в Джава.

ПреимуществаНедостатки
Повышение производительности — главный плюс многопоточности, ведь быстрое выполнение сложных операций — залог качественной программы.Трудности в отладке кода. Создание потоков — сложный процесс, их взаимодействие — еще сложнее, его трудно отследить, поэтому ошибки в таком коде искать проблематично.
Читаемый и структурированный код. Эти свойства кода значимы для командной разработки, также структурированный код проще поддерживать и расширять.Важность синхронизации. Программисту необходимо корректно организовать синхронизацию во избежание серьезных дефектов программы.
Мультипоточность — это правильное распределение таких ресурсов, как память, CPU, входные и выходные устройства и других.Более сложная архитектура приложения по сравнению с однопоточными приложениями, а значит, большие затраты. 
Улучшение пользовательского опыта. Интерфейс программы остается доступным даже при выполнении сложных фоновых задач.Программист должен понимать, когда стоит применять многопоточность, а когда нет. Некорректное применение только снизит производительность программы.