Насколько я понимаю, одной из причин перехода на реактивную (например, Project Reactor , RxJava, Vert-X ) или актерскую (Akka) структуру является высокая стоимость переключения потоков. Глядя на разницу между потоком платформы, потоком-носителем и виртуальным потоком в контексте Java 21 и более поздних версий и другую информацию о виртуальных потоках, мне было интересно...
Устраняют ли виртуальные потоки причину перехода на другую парадигму, поскольку они просто заменят блокирующий виртуальный поток на другой поток на носителе?
Связанный: Преимущества/недостатки реактивного программирования , особенно этот ответ.
Потоки платформы в Java сопоставляются непосредственно с потоком операционной системы хоста потока. Эти потоки ОС «дорогие» с точки зрения памяти и процессора.
Виртуальные потоки, напротив, управляются внутри JVM. В результате виртуальные потоки чрезвычайно «дешевы», то есть достаточно эффективны как в памяти, так и в процессоре. Благодаря виртуальным потокам вы можете разумно рассчитывать на одновременное выполнение даже миллионов задач на обычном компьютерном оборудовании.
Да, большая часть, если не вся, работа, выполняемая в реактивном коде, может быть выполнена с помощью виртуальных потоков Java. Код гораздо проще писать, понимать, отслеживать и отлаживать. Реактивный подход был изобретен, чтобы обойти проблемы производительности, связанные с чрезмерным использованием потоков платформы.
Посмотрите видео , где Брайана Гетца спрашивают, каким он видит будущее реактивного программирования после появления Project Loom и виртуальных тредов:
Я думаю, что Loom убьет реактивное программирование… реактивное программирование было переходной технологией…
Отказ от реактивного программирования был одной из основных причин изобретения виртуальных потоков в Java.
Предостережение... Виртуальные потоки противопоказаны для задач, связанных с процессором , таких как кодирование/декодирование видео. Используйте виртуальные потоки только для кода, который включает блокировку, например ведение журналов, файловый ввод-вывод, доступ к базам данных, сетевые вызовы. Это охватит почти все бизнес-приложения Java.
Конечно, задачи, выполняемые этими многочисленными дешевыми виртуальными потоками, могут потребовать значительных ресурсов. Эти ресурсы могут включать в себя потребление большого объема памяти, связывание ограниченного числа сетевых портов, перегрузку базы данных слишком большим количеством подключений и т. д.
В прежние времена Java-программисты узнали, что небольшое количество потоков платформы, которые были практичны для многопоточности, косвенно ограничивало чрезмерное использование ресурсов выполняющимися задачами. Теперь, когда потенциально существуют миллионы виртуальных потоков, вам может потребоваться добавить явное регулирование дорогостоящих задач для экономии драгоценных ресурсов. Вы можете использовать механизмы регулирования, такие как разрешения Semaphore или ReentrantLock.
Подробнее см. Ответ Тедди.
Слабость текущей реализации виртуальных потоков заключается в том, что в некоторых случаях виртуальный поток «прикреплен» к потоку своей несущей платформы, то есть его нельзя отложить, пока он заблокирован, для назначения этому потоку несущей платформы другого виртуального потока.
Начиная с Java 22, закрепление происходит как минимум в двух сценариях:
synchronized
код.Чтобы внести ясность: вы можете использовать synchronized
и собственный код в своих виртуальных потоках. Но если выполняемая задача включает в себя значительно длительные периоды такой работы, то назначьте эту задачу потоку платформы, а не виртуальным потокам. Или, в случае длинного кода, защищенного synchronized
(долгодействующий код, а не весь код), замените на ReentrantLock. Чрезмерное закрепление снижает эффективность и результативность использования других виртуальных потоков.
Команда Project Loom продолжает свою работу. Они ищут способы уменьшить количество ситуаций, приводящих к закреплению. Так что ситуация может измениться в будущих версиях Java.
Вы можете легко обнаружить длительное закрепление. Java будет генерировать новое событие JDK Flight Recorder (JFR), jdk.VirtualThreadPinned
каждый раз, когда закрепляется виртуальный поток, с порогом 20 мс по умолчанию.
Подробности см. в первую очередь в официальном документе JEP 444: Виртуальные потоки . См. также официальный сайт Project Loom.
Затем посмотрите недавние видео презентаций Рона Пресслера, Алана Бейтмана или Хосе Полара. В публичном доступе на YouTube и т. д.
Если вы действительно хотите понять, как был достигнут впечатляющий прирост производительности, посмотрите выступление Рона Пресслера на Продолжения : Продолжения - Под обложками. Чтобы внести ясность: это понимание совершенно необязательно и необязательно для эффективного использования виртуальных потоков. Но если вам нужно удовлетворить свое компьютерное любопытство, вам понравится именно эта речь Пресслера.
Это могло бы устранить необходимость в реактивном подходе, реализованном в коде Java, но, ИМХО, вопросы и ответы вводят в заблуждение, указывая, что виртуальные потоки являются заменой упомянутых платформ. На самом деле, в другом ответе все еще есть соответствующая статистика, над которой стоит задуматься.
@Naman Я добавил два раздела, посвященных дорогостоящим задачам и закреплению, чтобы более четко определить соответствующие случаи, когда виртуальные потоки могут заменить реактивное программирование.
Виртуальный поток бесполезен, если клиент API выполняет блокирующий вызов API, например OkHttpClient. Клиент API также должен быть неблокирующим, как Spring Webclient, который снова использует реактивные библиотеки.
Виртуальные потоки устраняют большинство проблем с тяжелыми потоками ОС, которые решало реактивное программирование.
Однако с помощью виртуальных потоков вы все равно можете перегрузить целевые ресурсы.
Когда у нас было всего от 500 до 2000 потоков, мы создавали пулы потоков для веб-запросов и т. д., поэтому регулирование осуществлялось прямо в точке входа.
Теперь, когда мы вообще не регулируемся в точке входа (скажем, веб-сервер использует новую модель виртуального потока для каждого запроса), тогда все запросы теперь будут проходить через систему и попадать в БД или файловый ввод-вывод или Сторонний сервис.
Итак, нам нужно регулировать вручную с помощью ограничений скорости и очередей или снова использовать реактивные структуры и противодавление. Если мы этого не сделаем, мы можем быть заблокированы из-за других узких мест в ресурсах или ограничений скорости нисходящего API/нас могут забанить/заблокировать.
То же самое относится и к процессору. Несколько задач, связанных с ЦП, все еще могут перегружать ЦП и припарковать 10 000 виртуальных потоков.
Таким образом, регулирование каждого ресурса должно осуществляться более осознанно.
Для этого у нас есть семафор в Java, а в Helidon 4 (на основе VirtualThread) вы можете использовать перегородку отказоустойчивости (модный семафор).
Устраняют ли виртуальные потоки причину перехода на другую парадигму [например, реактивную]
Теоретически да, это намерение, но реальность слишком сложна, чтобы дать простой ответ. Исследование, проведенное Дэниелом О в его статье на DZone Демистификация производительности виртуальных потоков: раскрытие истины за пределами шумихи, показывает, что ничто, включая виртуальные потоки, не может превзойти производительность неблокирующего реактивного приложения, основанного на цикле событий.
Обратите внимание, что тесты включали не только операции с интенсивным использованием процессора , описанные выше , которые действительно не очень подходят для виртуальных потоков.
Такая разница в производительности вполне понятна: неблокирующая реактивная парадигма сводит к минимуму переключение контекста потока: либо полномасштабное, на основе ОС, либо облегченное, на основе виртуального потока/продолжения. Однако эта парадигма требует довольно строгого поведения, когда дело доходит до блокировки и неблокировки: реактивно-совместимый код должен быть полностью неблокирующим. Действительно, если только один метод в стеке выполнения не будет неблокирующим, то «одиночный» цикл событий застревает, и все приложение блокируется. Обратите внимание, что это требование применяется не только к коду, который пишет разработчик, но и к библиотекам, которые этот код использует. Вот почему для реактивного программирования требуются неблокирующие платформы и библиотеки, такие как веб-серверы, Netty или Helidon 3, Http-клиент, реактивные драйверы JDBC и так далее. Реактивное поведение драйвера JDBC настолько специфично, что Oracle, например, ввела специальные JDBC Reactive Extensions .
Излишне говорить, что такая стратегия развития – не самый простой путь и не всегда может быть доступным. Вместо этого виртуальные потоки предлагают (относительно) быстрый и простой способ использования и замены, за счет, конечно, производительности по сравнению с реактивной парадигмой.
В заключение, если вам нужна высочайшая производительность в вашем многопоточном приложении и вы можете позволить себе тщательную, полностью неблокирующую разработку, то виртуальные потоки не могут стать заменой реактивной парадигмы.
В общем, ваш вопрос слишком глубокий и сложный для ТАКОГО ответа и краткого последующего обсуждения и, ИМХО, заслуживает более детального исследования. Кроме того, легко увидеть, что в приведенном выше объяснении все понятия, такие как «цикл одного события», доведены до крайности, в то время как реальность намного сложнее. Например, в Netty есть не один, а «несколько» циклов событий, где «несколько», как всегда, очень свободно определено. «Полностью» неблокирующийся код — еще один хороший пример.
Таким образом, согласно исследованию, производительность пропускной способности и ответа обычно одинакова для реактивных и виртуальных машин и составляет около 3,5 тыс. запросов в секунду, что более чем достаточно для большинства компаний (у Google около 40 тыс. запросов в секунду). думать). Кроме того, у вас есть дополнительный прирост процессора на 10% и 80-100 МБ оперативной памяти при примерно 3,5 тыс. запросов в секунду. Итак, вопрос: «Готов ли я изменить всю парадигму программирования с императивной на реактивную, чтобы получить 10% процессора и 80-100 МБ оперативной памяти?» Я категорически против. Проект все еще продолжается, и впереди нас ждут многообещающие улучшения.
Если бы ваша «причина перехода на другую парадигму» заключалась в том, что потоки блокировались для ввода-вывода с существующей реализацией, только тогда, возможно, да. Я сомневаюсь, что можно иначе сравнить все эти библиотеки/фреймворки с функцией JDK в одном месте. Кроме того, согласно сайту, это, как правило, не по теме.