1. Введение
Часто в наших приложениях нам нужно иметь возможность делать несколько вещей одновременно. Мы можем добиться этого несколькими способами, но ключевым из них является реализация многозадачности в той или иной форме.
Многозадачность означает выполнение нескольких задач одновременно , при этом каждая задача выполняет свою работу. Обычно все эти задачи выполняются одновременно, читая и записывая одну и ту же память и взаимодействуя с одними и теми же ресурсами, но выполняя разные действия.
2. Нативные темы
Стандартный способ реализации многозадачности в Java — использование потоков . Многопоточность обычно поддерживается операционной системой. Мы называем потоки, работающие на этом уровне, «нативными потоками».
Операционная система имеет некоторые возможности многопоточности, которые часто недоступны для наших приложений просто из-за того, насколько она ближе к базовому оборудованию. Это означает, что выполнение собственных потоков обычно более эффективно. Эти потоки напрямую сопоставляются с потоками выполнения на ЦП компьютера, а операционная система управляет сопоставлением потоков с ядрами ЦП.
Стандартная модель потоков в Java, охватывающая все языки JVM, использует собственные потоки . Это имело место, начиная с Java 1.2, и имеет место независимо от базовой системы, в которой работает JVM.
Это означает, что каждый раз, когда мы используем любой из стандартных механизмов потоковой передачи в Java, мы используем собственные потоки. Сюда входят java.lang.Thread
, java.util.concurrent.Executor
, java.util.concurrent.ExecutorService
и так далее.
3. Зеленые нити
В разработке программного обеспечения одной из альтернатив нативным потокам являются зеленые потоки . Здесь мы используем потоки, но они не сопоставляются напрямую с потоками операционной системы. Вместо этого базовая архитектура сама управляет потоками и управляет тем, как они отображаются на потоки операционной системы.
Обычно это работает, запуская несколько собственных потоков, а затем выделяя зеленые потоки на эти собственные потоки для выполнения . Затем система может выбрать, какие зеленые потоки активны в любой момент времени и в каких собственных потоках они активны.
Это звучит очень сложно, и это так. Но это осложнение, о котором нам обычно не нужно заботиться. Базовая архитектура позаботится обо всем этом, и мы можем использовать ее, как если бы это была нативная модель многопоточности.
Так зачем нам это делать? Собственные потоки очень эффективны для запуска, но их запуск и остановка требуют больших затрат. Зеленые потоки помогают избежать этих затрат и придают архитектуре большую гибкость. Если мы используем относительно долго работающие потоки, то нативные потоки очень эффективны. Для очень недолговечных заданий стоимость их запуска может перевесить выгоду от их использования . В этих случаях зеленые потоки могут стать более эффективными.
К сожалению, в Java нет встроенной поддержки зеленых потоков.
В очень ранних версиях в качестве стандартной модели многопоточности использовались зеленые потоки вместо собственных потоков. Это изменилось в Java 1.2, и с тех пор не было никакой поддержки на уровне JVM.
Также сложно реализовать зеленые потоки в библиотеках, потому что для хорошей работы им потребуется очень низкоуровневая поддержка. Таким образом, часто используемой альтернативой являются волокна.
4. Волокна
Волокна представляют собой альтернативную форму многопоточности и аналогичны зеленым нитям . В обоих случаях мы не используем собственные потоки, а вместо этого используем базовые системные элементы управления, которые выполняются в любое время. Большая разница между зелеными нитями и волокнами заключается в уровне контроля и, в частности, в том, кто контролирует.
Зеленые потоки — это форма упреждающей многозадачности. Это означает, что базовая архитектура полностью отвечает за решение о том, какие потоки выполняются в любой момент времени.
Это означает, что применимы все обычные проблемы многопоточности, когда мы ничего не знаем о порядке выполнения наших потоков или о том, какие из них будут выполняться одновременно. Это также означает, что базовая система должна иметь возможность приостанавливать и перезапускать наш код в любое время, возможно, в середине метода или даже оператора.
Вместо этого волокна представляют собой форму совместной многозадачности, означающую, что работающий поток будет продолжать работать до тех пор, пока не даст сигнал о том, что он может уступить место другому потоку . Это означает, что мы несем ответственность за взаимодействие волокон друг с другом. Это дает нам прямой контроль над тем, когда файберы могут приостановить выполнение, вместо того, чтобы система решала это за нас.
Это также означает, что нам нужно писать наш код таким образом, чтобы это было возможно. В противном случае это не сработает. Если в нашем коде нет точек прерывания, то можно вообще не использовать волокна.
В настоящее время в Java нет встроенной поддержки волокон. Существуют некоторые библиотеки, которые могут представить это в наших приложениях, включая, помимо прочего:
4.1. Квазар
Quasar — это библиотека Java, которая хорошо работает с чистой Java и Kotlin и имеет альтернативную версию, которая работает с Clojure.
Он работает за счет наличия агента Java, который должен работать вместе с приложением, и этот агент отвечает за управление волокнами и обеспечение их правильной совместной работы. Использование агента Java означает, что не требуется никаких специальных шагов сборки.
Quasar также требует, чтобы Java 11 работала корректно, что может ограничить количество приложений, которые могут его использовать. В Java 8 можно использовать более старые версии, но они активно не поддерживаются.
4.2. килим
Kilim — это Java-библиотека, которая предлагает функции, очень похожие на Quasar, но делает это за счет использования переплетения байт-кода вместо агента Java . Это означает, что он может работать в большем количестве мест, но усложняет процесс сборки.
Kilim работает с Java 7 и новее и будет работать корректно даже в тех случаях, когда агент Java невозможен. Например, если другой уже используется для контрольно-измерительных приборов или мониторинга.
4.3. Проект Ткацкий станок
Project Loom — это эксперимент проекта OpenJDK по добавлению волокон в саму JVM, а не в виде дополнительной библиотеки . Это даст нам преимущества волокон перед нитями. Реализация его непосредственно на JVM может помочь избежать осложнений, которые вносят агенты Java и переплетение байт-кода.
Текущего графика выпуска Project Loom нет, но мы можем скачать бинарные файлы раннего доступа прямо сейчас, чтобы посмотреть, как идут дела. Однако, поскольку это еще очень рано, мы должны быть осторожны, полагаясь на это для любого производственного кода.
5. Совместные процедуры
Сопрограммы — это альтернатива многопоточности и волокнам. Мы можем думать о сопрограммах как о волокнах без какой-либо формы планирования . Вместо того, чтобы базовая система определяла, какие задачи выполняются в любое время, наш код делает это напрямую.
Как правило, мы пишем сопрограммы так, чтобы они выполнялись в определенных точках своего потока. Их можно рассматривать как точки паузы в нашей функции, где она перестанет работать и, возможно, выдаст какой-то промежуточный результат. Когда мы уступаем, мы останавливаемся до тех пор, пока вызывающий код не решит перезапустить нас по какой-либо причине. Это означает, что наш вызывающий код управляет планированием того, когда это будет выполняться.
Kotlin имеет встроенную поддержку сопрограмм, встроенную в его стандартную библиотеку. Есть несколько других библиотек Java, которые мы можем использовать для их реализации, если это необходимо.
6. Заключение
В нашем коде мы видели несколько различных вариантов многозадачности, начиная от традиционных нативных потоков и заканчивая очень легкими альтернативами. Почему бы не попробовать их в следующий раз, когда приложению потребуется параллелизм?