JavaRush /Java блог /Java Developer /Многопоточность в Java
Автор
Pavlo Plynko
Java-разработчик в CodeGym

Многопоточность в Java

Статья из группы Java Developer

Введение

Прежде, чем узнать про потоки Java, давайте заглянем в недалёкое будущее. Представьте, что вы подали резюме и прошли собеседование. Вас и пару дюжин будущих коллег пригласили на работу в большую Software-компанию. Среди прочих хлопот нужно подать бумажные документы для трудоустройства уставшему сотруднику HR-отдела.
Многопоточность в Java - 1
Чтобы ускорить процесс, претендентов на должность можно разделить на две группы и распределить их между двумя HR-менеджерами (если таковые есть в компании). В результате мы получаем ускорение процесса за счёт параллельной (parallel) работы по оформлению.
Многопоточность в Java - 2
Если же кадровик в компании один, то придётся как-то выкручиваться. Например, можно снова- таки разбить всех на две группы, например, собеседовать поочерёдно девушек и юношей.
Многопоточность в Java - 3
Или по другому принципу: так как в нижней группе больше народа, будем чередовать на одного юношу двух девушек.
Многопоточность в Java - 4
Такой способ организации работы называется многопоточным. Наш утомлённый кадровик переключается на разные группы для оформления из них очередного сотрудника. Групп, может быть, одиннадцать, а кадровиков – четыре. В этом случае многопоточная (multithreading) обработка будет происходить параллельно несколькими HR-ами, которые могут брать очередного человека из любой группы для обработки его документов.

Процессы

Процессом (process) в данном случае будет организация работы приёма документов. В организации можно выделить несколько процессов: бухгалтерский учёт, разработка ПО, встречи с клиентами, работа склада и т. д. На каждый процесс выделены ресурсы: помещение, сотрудники для его исполнения. Процессы изолированы друг от друга: у кадровиков отсутствует доступ в бухгалтерскую базу, а менеджеры по работе с клиентами не бегают по складу. Если процесс должен получить доступ к чужим ресурсам, необходимо наладить межпроцессное взаимодействие: служебные записки, совместные совещания.

Потоки

Работа в процессе организована в виде потоков (java thread). Для отдела кадров, поток – это организация работы по обслуживанию группы. На самой первой картинке – один поток, последующих трёх – два. Внутри процесса потоки могут выполняться параллельно – два кадровика принимают две или более группы будущих сотрудников. Взаимодействие кадровиков с группами – обработку потоков внутри процесса – называют синхронизацией потоков. На рисунках оформления одним кадровиком двух групп видны показаны способы: равномерный (девушка – юноша – девушка – юноша) и с разными приоритетами (две девушки чередуются с одним юношей). Потоки имеют доступ к ресурсам процесса, к которому они относятся: группам к кадровику даны образцы бланков заявлений, ручки для заполнения документов. Но если потоки взаимодействуют с общими для них вещами – то возможны казусы. Если кадровик попросит крикнуть имя последнего человека в очереди – то, в случае с двумя группами, он не уверен заранее, что услышит женское имя или мужское. Подобные конфликты доступа к данным, блокировки и способы их разрешения – очень важная тема.

Состояния потока

Каждый поток пребывает в одном из следующих состояний (state):
  • Создан (New) – очередь к кадровику готовится, люди организуются.
  • Запущен (Runnable) – наша очередь выстроилась к кадровику и обрабатывается.
  • Заблокирован (Blocked) – последний в очереди юноша пытается выкрикнуть имя, но услышав, что девушка в соседней группе начала делать это раньше него, замолчал.
  • Завершён (Terminated) — вся очередь оформилась у кадровика и в ней нет необходимости.
  • Ожидает(Waiting) – одна очередь ждёт сигнала от другой.
Организация потоков и их взаимодействие – это основа эффективной работы процессов.

Вернемся в IT-мир

В XXI веке многопоточное и параллельное выполнение стало актуальным. С 90-х годов прошлого века многозадачные операционные системы Windows, MacOS и Linux прочно обосновались на домашних компьютерах. В них часто можно встретить четырёх- и более ядерные процессоры. Число параллельных блоков GPU-видеокарт уже перевалило за тысячу. Популярные программы пишутся с учетом многопоточности (multithreading), например, современные версии ПО обработки графики, видео или оперирующих большим объемом данных: Adobe Photoshop, WinRar, Mathematica, современные игры. Многопоточность Java – очень важная, востребованная и сложная тема. Поэтому в курсе JavaRush встречается много задач, чтобы разобраться с ней очень хорошо. Java-примеры на многопоточность помогут освоить основные нюансы и тонкости этой области, синхронизации работы потоков.

Процесс

Process (процесс) – выполняющийся экземпляр программы, которому Операционная Система (ОС) выделила память, процессорное время/ядра и прочие ресурсы. Важно, что память выделяется отдельно, адресные пространства различных процессов недоступны друг другу. Если процессам необходимо обмениваться данными, они могут это сделать с помощью файлов, каналов и иных способов межпроцессного взаимодействия.

Поток

Java Thread (поток). Иногда, чтобы не путать с другими классами Java – Stream и подобными, потоки Java часто переводят как нить. Они используют выделенные для процесса ресурсы и являются способом выполнения процесса. Главный поток выполняет метод main и завершается. При выполнении процесса могут порождаться дополнительные потоки (дочерние). Потоки одного процесса могут между собой обмениваться данными. Многопоточность Java требует учитывать синхронизацию данных, не забывайте об этом. В Java процесс завершается тогда, когда закончил работу последний его поток. Для фоновых задач поток можно запустить как демон (daemon), отличие которого от обычного в том, что они будут принудительно завершены при окончании работы всех не-daemon потоков процесса.

Первое многопоточное приложение

Существует более полудюжины способов создания потоков, в рамках JavaRush курса мы их подробно разберём. Для начала познакомимся с одним из базовых. Имеется специальный класс Thread в методе run() которого необходимо написать код, реализующий логику программы. После создания потока, можно запустить его, вызвав метод start(). Напишем демонстрационную программу, реализующую пример многопоточности Java.

class PeopleQueue extends Thread    {// Наша очередь из сотрудников, наследник класса Thread
    private String[] names;

    PeopleQueue(String... names) {// Конструктор, аргумент- массив имен сотрудников
        this.names = names;
    }

    @Override
    public void run() { // Этот метод будет вызван при старте потока
        for (int i = 0; i < names.length; i++) { // Вывод в цикле с паузой 0.5 сек очередного сотрудника
            System.out.println("Обработаны документы: " + names[i]);
            try {
                sleep(500); // Задержка в 0.5 сек
            } catch (Exception e) {}
        }
    }
}

public class HR    {// Класс для демонстрации работы потока
    public static void main(String[] args) {
        // Создаем две очереди
        PeopleQueue queue1 = new PeopleQueue("Иван","Сергей","Николай","Фердинанд","Василий");
        PeopleQueue queue2 = new PeopleQueue("Мария","Людмила","Алиса","Карина","Ольга");

        System.out.println("Начали!"); // Сообщение из главного потока программы
        queue1.start();    //Запускаем одну очередь (дочерний поток)
        queue2.start(); //Запускаем вторую (дочерний поток)
    }
}
Запустим программу. В консоли виден вывод сообщения главным потоком. Далее, каждый дочерний поток queue1 и queue2 поочередно выводят сообщения в общую для них консоль об очередном обработанном сотруднике. Один из возможных вариантов работы программы:

Начали!
Обработаны документы: Мария
Обработаны документы: Иван
Обработаны документы: Людмила
Обработаны документы: Сергей
Обработаны документы: Алиса
Обработаны документы: Николай
Обработаны документы: Карина
Обработаны документы: Фердинанд
Обработаны документы: Ольга
Обработаны документы: Василий

Process finished with exit code 0
Многопоточность в Java – тема трудная и многосторонняя. Умение писать код с использованием параллельных, многозадачных и многопоточных вычислений поможет вам эффективно реализовать задачи на современных многоядерных процессорах и кластерах, состоящих из множества компьютеров.
Комментарии (8)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Anonymous #3109741 Уровень 20
25 августа 2023
В примере кода метод run() для вызова старта потока, а ниже потом мы вызываем queue1.start(). Объясните почему, пожалуйста.
Kosarev Уровень 20
3 января 2022
хотим еще
YesOn Уровень 13
27 ноября 2021
Для начального этапа изучения потоков - хорошая статья с очень простым и понятным примером!🙂👍
Dmitry Gebeydullov Уровень 18
10 января 2021
Алексей Уровень 17
3 ноября 2020
И ни слова про интерфейс Runnable. Оригинально.
Anonymous #1704678 Уровень 25
3 декабря 2018
А где же продолжение банкета? wait, notify, executor?
shteynu Уровень 19
23 октября 2018
а есть какая то статья более развернутая на русском языке, ну или на английском, за исключением оракл тьюториал.
Мухамед Уровень 31
25 июня 2018
Спасибо что с таким примером объяснили, теперь начал понимать что такое потоки.