Проект Loom добавит в Java 19 виртуальные треды. Что это? Новые перспективы для рынка труда нарисовались в предыдущей части заметки. В аспекте внутренностей JVM про Loom рассказывает Иван Углянский: рекомендую его доклад «Thread Wars — проект Loom наносит ответный удар».

Здесь мы оценим, как добавка повлияет на современные подходы серверной разработки. Потеснит ли новинка Scala и Kotlin с их фреймворками? Заодно ответим на вопрос «а в какой мере Loom — реактивный»?

Vert.x и Loom

C точки зрения Vert.x, новая добавка в Java — это не конкурирующий фреймворк, а движок. Сейчас Vert.x использует в качестве движка Netty. Что Vert.x выиграет, если перейдёт на Loom? Вменяемые стектрейсы. Они сильно упростят отладку и сопровождение реактивного приложения. Разработчики Vert.x прекрасно понимают это. В проекте уже имеется отдельная ветка для интеграции с Loom.

Spring и Loom

Дадим слово разработчикам Spring:

Первое, что приходит мне в голову — это spring-web*. Реализации [на базе] WebMvc — железобетонные и простые, тогда как реализации [на базе] WebFlux — масштабируемые и реактивные. Последнее идёт вместе с недостатками в виде усложнения кода, худшей читаемости и малой пригодности для отладки. Loom же обещает достигнуть масштабируемости путём порождения миллионов Фибров, и в то же время оставить код читаемым, сохраняя его организацию последовательной.

— эта исследовательская задача была включена в контрольную точку шестой версии Spring сразу после объявления о поставке Loom в JDK 19.

Реактивность и Loom

Манифест Йонаса Бонера (2014 год) определяет Реактивную систему как:

  • Отзывчивую: отвечает по возможности быстро.

  • Устойчивую: справляется со сбоями и делает это сама.

  • Эластичную: автоматически увеличивает и уменьшает потребление ресурсов в зависимости от нагрузки. Эластичность включает масштабирование.

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

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

В случае Loom техническим решением являются не обмен сообщениями, а легковесные потоки (или фибры, или горутины). Строго говоря, по Бонеру Loom — нереактивный. При этом он асинхронный и неблокирующим образом обрабатывает сообщения. С ним можно обеспечить отзывчивость и масштабируемость системы, как справедливо замечают разработчики Спринга. Из реактивного джентльменского набора проекту не хватает обратного давления и аналога мониторируемых очередей.

Обратное давление нужно, чтобы справляться с неравномерной нагрузкой. Сторонние библиотеки, вроде Vert.x, могут помочь с реализацией.

Очереди нужны, чтобы отслеживать изменяющуюся нагрузку. Пару лет назад техлид проекта Рон Преслер упомянул, что Даг Ли уже начал разработку каналов (неблокирующих очередей). Однако результатов пока не заметно ни в одном JEP.

Резюмируем, что в практическом смысле Loom обладает свойствами реактивной системы или решает её задачи.

Loom и реактивность нового поколения

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

Специальная языковая конструкции try-with-resources появилась ещё в Java 7. Проблема в том, что этот механизм не работает в асинхронном режиме.

Scala-библиотека Cats Effect 3 авторства Спивака вводит дополнительные примитивы управления ресурсами.

И с ними можно писать, например, код гонок:

val uri1 = “jdbc:postgress:replica1”
val uri2 = “jdbc:postgress:replica2”

val joint: Resource[IO, Connection] =
connect(uri1).race(connect(uri2)).map(_.merge)

В этом случае происходит одновременное подсоединение к двум репликам БД. В результате дальнейший код будет использовать более быстрое соединение. Опоздавшее будет корректно финализировано и связанные с ним ресурсы освободятся.

Важный сценарий, который запускает освобождение ресурсов — отмена запроса. Сначала по заданию клиента сервер обязан произвести некоторые действия. В процессе выполнения действий поступает сигнал об отмене. Аннулировать запрос может и клиент, и сработавший таймаут, и даже системный сбой.

Реактивность нового поколения предусматривает первоклассную поддержку:

  • гарантированного освобождения ресурсов;

  • отмены асинхронных операций с высвобождением ресурсов;

  • в частности, остановки по таймауту.

Если мы заглянем в Vert.x или Spring WebFlux, то не найдём там ничего подобного. Почему? Потому что ни библиотеки, на которых базируются эти фреймворки, ни сама Java не предоставляют примитивов, на чьей основе можно было бы построить нужные операции или методы.

Поможет ли в этом проект Loom? В рамках JEP 425 — нет. А вот сопутствующий JEP 428 описывает Структурную конкурентность (SC) как новую добавку в Java 19.

Подход SC реализован на самых разных платформах. Например, в библиотеке Котлиновских корутин. Вкратце, SC предоставляет альтернативный примитивам Cats Effect 3 способ управлять ресурсами при асинхронной организации кода. Там также возможны отмены и таймауты. Пока JEP 428 делает упор на управление исключительно тредами.

Мы видим, что разработчики Java нацелены придать языку способность строить системы уровня реактивных нового поколения. Пока эти намерения находятся на стадии инкубации. Открытым вопросом остаётся, получится ли у фреймворков высокого уровня (Vert.x, Spring и так далее) опереться на новые базовые примитивы. Как заметил создатель Java Джеймс Гослинг: «Непросто придумать семантику, которая бы работала как следует».

Заключение

В Java приходят изменения, которые позволят с одной стороны реализовать отзывчивые и масштабируемые системы, а с другой — облегчить сопровождение асинхронного кода. Процесс этот только начался. Сегодня для нового проекта на базе JDK лучше будет выбрать что-нибудь вроде Cats Effect 3 или Котлиновских корутин. Java намерена составить им конкуренцию, но когда-нибудь в отдалённом будущем и с пока неясным результатом.

В следующей части заметки мы мигрируем игрушечный проект на Loom. Посмотрим, каких усилий это потребует и что именно поменялось в результате.

Комментарии (10)


  1. upagge
    04.06.2022 19:19
    +2

    Прикольно рекомендовать доклад, который еще не вышел))


    1. Sap_ru
      04.06.2022 20:57
      +4

      Прикольно мешать реактивность с многопоточностью. По критериям автора старая добрая Java даже без всяких легковесных потоков реактивна на 146%.


    1. a_a_vasiljev Автор
      05.06.2022 07:46

      Пардон, Loom к нам только осенью в Java 19 приедет, да и то в виде превью фичи. А доклад состоится уже на днях и в готовом к употреблению виде. Ну как его не порекомендовать?


    1. maxzh83
      05.06.2022 14:19
      +1

      При этом были доклады по теме, которые уже вышли. Например, вот этот https://jokerconf.com/talks/project-loom-a-friend-or-foe-of-reactive/


  1. tmk826
    04.06.2022 22:21

    По моему единственное что даёт loom это забыть о cachedThreadPool. Все остальные проблемы всё равно надо решать (синхронизации, эксклюзивный доступ к ресурсам, лимиты). Надо конечно сказать, что замена cachedThreadPool на фиберы в моем (конкреном) случае дало 10% прироста производительности.


    1. BugM
      05.06.2022 01:05

      Много почти бесплатных потоков это очень классно для серверов и фреймворков. Spring, Jetty и все подобное станут быстрее и при этом не потеряют простоту.

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


    1. grobitto
      05.06.2022 11:06
      +1

      фибер будет освобождать поток для других фиберов при блокирующих операциях типа io итд, по факту это замена асинк лапши с коллбэками на красивый линейный код со всеми плюшками типа красивыйх стектрейсов итд.

      А прирост производительности будет в зависимости от типа приложения, если асинк переписать на фиберы прироста может вообще не быть, просто код станет намного проще


      1. snuk182
        05.06.2022 16:22
        -1

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


  1. vit1252
    06.06.2022 12:53

    Это что-то вроде async/await или о чем вообще речь?


    1. a_a_vasiljev Автор
      06.06.2022 12:59

      И да, и нет. "Да", потому что добавляется возможность перехода на легковесные программные потоки. То есть работать будет (семантика) похоже на async / await. "Нет", потому что язык расширять (менять синтаксис) не будут. Обойдутся изменениями библиотечных функций. Старые-добрые Джава экзекуторы, форки и джойны без асинков и эвэйтов.