Java Concurrency или как работают потоки: применение на практике
https://t.me/data_analysis_mlДля того, чтобы успешно строить карьеру программиста, необходимо устроиться работать по соответствующей профессии, а также знать особенности языков программирования. Огромным спросом у разработчиков на данный момент пользуется Java. Это – кроссплатформенный универсальный вариант «общения» с компьютерами и программным обеспечением. Прост в освоении, обладает относительно понятным синтаксисом. Многофункционален, имеет возможность ООП.
Когда будущий разработчик проходит собеседование, ему задают разнообразные вопросы по коддингу. В случае с Java немаловажным моментом является тема потоков. Особенно многопоточности. Данная статья поможет разобраться в соответствующем направлении, а также составить приложение, использующее Java Concurrency.
Процесс – определение
Рассматривая упомянутую тему, нельзя обойти стороной такое понятие как «процесс». Это – основа, на которой базируется многопоточность.
Каждый программист должен уяснить следующее:
- Процесс представляет сочетание кода и информации. Он создается ОС при запуске приложения. Служит виртуальным адресным пространством.
- Процессы на задействованном устройстве функционируют обособленно друг от друга.
- Нет прямого доступа к общей информации в других процессах, работающих на девайсе.
- Для успешной работы требуется выделение тех или иных ресурсов – памяти и времени. Эти задачи возлагаются непосредственно на операционную систему.
- Если один из процессов блокируется, новый не может работать. Это происходит до тех пор, пока первый, заблокированный, не будет разлочен.
- Для создания очередного нового процесса принято использовать родительские. В ходе реализации поставленной задачи будет проведено дублирование.
- Process-родитель умеет контролировать дочерние операции. Обратная связь невозможна ни при каких обстоятельствах.
Эти принципы и особенности актуальны не только для операций, выполняющихся в приложении, написанном на Java. Под процессом принято понимать связь кода и информации, которые делят между собой адресное пространство.
Потоки – что это и с чем их едят
В Java поток – это единица реализации программного кода. Последовательность данных, которая могут работать параллельно с другими своими «аналогами».
Поток отвечает за выполнение инструкций запущенного процесса, к которому он относится. Все это происходит параллельно с иными потоками этого же process. Является легковесным. Может «общаться» с другими потоками. Для реализации поставленной задачи требуется применение специальных методов.
Какими могут быть потоки – классификация
Собеседование по Java и многопоточности обязательно предусматривает вопросы по «обычным» потокам в программировании. Стоит классифицировать оные. Это помогает разобраться, за что отвечает тот или иной «вариант».
Существует разделение потоков по типу движения информации:
- вводные – информация поступает в утилиту, после чего считывается;
- выводные – программа передает те или иные сведения, осуществляется запись в потоки.
Присутствует разделение по типу передаваемых электронных материалов. Не во всех случаях программисты используют байты. В Java и других языках может использоваться текст. На основании этого выделяют следующие потоки:
- символьные;
- байтовые.
У каждого приведенного примера существуют собственные абстрактные классы.
Принципы работы потоков
Перед использованием в Java многопоточности, нужно понимать, как будет работать каждая такая «операция». Многое зависит от того, какие манипуляции реализовываются. Чаще всего имеет место чтение и запись.
В Java потоки будут обладать примерно таким алгоритмом:
- Создается экземпляр необходимого потока.
- Последний открывается для дальнейшего считывания. При необходимости – для записи новой информации.
- Пользователь проводит задуманные изначально действия. В предложенном примере – чтение и запись информации.
- Осуществляется закрытие потока.
Создание и открытие экземпляра – это единый шаг. Остальные действия не реализовываются одновременно. Они воплощаются в жизнь последовательно.
Чем хороши потоки
В процессе программирования можно использовать различные элементы и операции. Потоки в Java имеют ряд преимуществ. К ним относят:
- Относительную легкость по сравнению с процессами. Это позволяет минимизировать ресурсные затраты для функционирования приложения.
- Быстрое переключение софта.
- Упрощенную схему взаимодействия процессов между собой.
У мелких утилит есть всего одна «нить». Это – главная нить (main thread). Дополнительно могут запускаться иные потоки. Они будут носить название дочерних. Главный thread отвечает за выполнение метода main, после чего завершается.
Определение многопоточности
Многопоточность Java – это поддержка одновременной работы более одного потока. В процессе выполнения приложения некоторые «операции» осуществляются параллельно друг другу, да еще и в «фоновом» режиме. Наглядные примеры:
- сигнальная обработка;
- операции с памятью;
- управление системой устройства/софта.
Приложение примет только первый поток. За счет многопоточности осуществляется одновременный прием и обработка нескольких потоков в рамках одной и той же утилиты.
Важно: многопоточность не имеет места с процессорами одноядерного типа. Там время процессорного характера делится между несколькими процессами и «открытыми» потоками.
Потоковая синхронизация – как понимать?
Определение синхронизации потоков имеет прямое отношение к многопоточности. Синхронизированный «участок» программного кода выполняется только одним потоком одновременно.
В Java присутствует поддержка одновременного выполнения нескольких подобных «элементов». Соответствующее явление иногда приводит к тому, что больше одного потока имеет доступ к одним и тем же полям/объектам.
Синхронизация – выполнение параллельных потоков в приложении синхронно. Способствует избеганию ошибок согласования памяти. При объявлении метода синхронизированным нить держит монитор для объекта. Если иной поток займется исполнением синхронизированного метода, первый будет блокирован.
Синхронизацию в Java реализовывают через зарезервированное слово synchronized. Он может применяться в классах для определения синхронизированных:
- блоков;
- методов.
Не применяется соответствующее слово ни в переменных, ни в атрибутах в процессе определения того или иного класса.
Потоковые состояния
Thread в Java встречается в нескольких состояниях:
- new – создан;
- runnable – запуск;
- blocked – блокировка;
- terminated – завершение;
- waiting – ожидание.
Первый «статус» присваивается при создании экземпляров класса, второй – после запуска и начала процессорной обработки. При «блокировке» поток ожидает высвобождения ресурсов, а также завершения ввода-вывода информации. При terminated происходит завершение работы без перспектив повторного запуска.
Также можно встретить Suspend. Это процесс приостановки работающего потока. Предусматривает продолжение с момента «паузы». Состояние Dead в Java возникает при полном прекращении работы Thread. Указывает на то, что жизненный цикл «объекта» подошел к концу.
Concurrency – библиотека для работы со Threads
У Джавы немало документации на русском языке, при помощи которой можно разобраться в принципах работы с языком. И там обязательно рассказывается о многопоточности. В процессе чтения соответствующей информации программеры встречают такое понятие как Concurrency.
Так называют специальную библиотеку Java. В ней собраны спецклассы, предназначенные для работы с несколькими нитями. Они включены в пакет java.util.concurren. Включают в себя различные элементы:
- Concurrent Collections – коллекции, предназначающиеся для работы с многопоточностью. В процессе работа используется принцип блокировки по сегментам информации. Возможна оптимизация параллельного чтения по wait-free алгоритмизации.
- Synchronizers – вспомогательный контент. Задействуется непосредственно при синхронизации потоковой информации. Особо важен для параллельных вычислений.
- Queues – очереди блокирующего и неблокирующего характера для многопоточности. Первый вариант применяет для «тормоза» потоков, если не проходят реализацию те или иные условия. Второй актуален для обеспечения скорости обработки информации. Функционирование в данном случае будет осуществляться без потоковой блокировки.
- Executions – фреймворки, использующиеся для создания потоковых пулов, а также при планировании асинхронных задач, для которых нужно выводить результаты.
- Locks – альтернативные способы синхронизации.
- Atomics – классы, поддерживающие атомарные операции со ссылками/различными примитивами.
Каждый «пакет» имеет собственные классы Java. Они отвечают за те или иные манипуляции при коддинге. Полную информацию о них можно изучить по этой ссылке.
Как создавать потоки: способы реализации
Пока не рассмотрены примеры многопоточности в языке Java, стоит уяснить, каким образом создаются threads. Существуют различные варианты развития событий. Все зависит от того, какие задачи предстоит реализовывать.
Создание потоковых «элементов» возможно через:
- класс, реализующий Runnable;
- классы, расширяющие Thread;
- реализацию java.util.concurrent.Callable.
Первый вариант встречается на практике чаще всего. Связано это с тем, что Java реализует интерфейс не в единственном количестве. Это дает возможность наследования классов.
Метод Runnable
На практике все перечисленные способы создания threads не слишком трудно реализовать. В случае с Runnable потребуется:
- создать объект класса thread;
- сделать class object, который реализовывает интерфейс Runnable;
- вызвать метод start() у объекта thread.
Все это поможет сделать new thread, а затем использовать его.
Метод Thread
Еще один вариант – наследование. Для этого предстоит:
- сделать class object ClassName extends Thread;
- обеспечить предопределение run() в соответствующем классе.
Позже будет приведен пример, в котором осуществляется передача имени потока «Second»
Через Concurrent
В этом случае потребуется:
- сделать объект класса, работающего с интерфейсом Callable;
- обеспечить создание ExecutorService, в котором пользователь указывает пул потокового характера;
- создать Future object.
Последний шаг осуществляется путем внедрения метода submit.
Наглядные примеры
Вот коды, которые пишем для создания new threads:
public static void howToRunThreads() { ThreadClass threadClass = new ThreadClass("First"); threadClass.start(); //method ThreadClass.run() Thread thread = new Thread(new RunnableClass("Second")); Thread thread2 = new Thread(new RunnableClass("Third")); Thread thread3 = new Thread(new RunnableClass("Fourth")); thread.start(); //method RunnableClass.run() thread2.start(); //method RunnableClass.run() thread3.start(); //method RunnableClass.run() } public class RunnableClass implements Runnable { private String localName; public RunnableClass() { } public RunnableClass(String localName) { this.localName = localName; } @Override public void run() { System.out.println("run() " + localName + " running"); } public String getLocalName() {return localName;} public void setLocalName(String localName) {this.localName = localName;} } public class ThreadClass extends Thread { public ThreadClass() { } public ThreadClass(String name) { super(name); } public ThreadClass(Runnable target) { super(target); System.out.println(target + " will running"); } @Override public void run() { System.out.println("ThreadClass run() method " + "Thread name is: " + this.getName()); } } //Выведение ThreadClass run() method Thread name is: First run() Third running run() Fourth running run() Second running
Если же нужно применять Callable, стоит обратить внимание на следующую кодификацию:
Для синхронизации методов можно реализовывать следующий код:
Для многих программистов при начале изучения многопоточных «элементов» Java становится проблемой принудительная остановка. На самом деле добиться желаемого результата не так трудно.
О принудительной остановке Thread
В Java 8 отсутствуют методы, при помощи которых можно добиться принудительной остановки. Но можно воспользоваться специальным механизмом, который позволяет вмешаться в потоковые процессы. Достаточно использовать interruption e. Это – механизм потокового оповещения.
У Thread есть булево поле – флаг прерывания. Устанавливается через вызов interrupt(). Проверить факт его задействования можно несколькими методами:
- через bool isInterrupted() потокового объекта;
- используя bool Thread.interruped().
В первом случае происходит возврат флага и его сброс. Во втором вызов осуществляется внутри thread, из которого был вызван метод. Данный прием дает возможность проверки состояния потокового прерывания в Java.
Здесь происходит следующее:
- В методе main() создается объект класса JoinClass, запускаемый через run.
- Происходит проверка на факт завершения. Каждые 100 секунд на экран выводится информация о значении счетчика.
- Главный метод ждет 1 000 мс для того, чтобы счетчик мог произвести расчеты.
- Осуществляется вызов interrupt у JoinClass.
- В цикле обнаруживается исключение.
- В разделе catch активируется return.
По принудительной потоковой остановке тоже много документации, как и по Java Concurrency гайдов на русском.