0
Обложка: Почему Java тормозит и не вводит новые фичи

Почему Java тормозит и не вводит новые фичи

Александр Васильев
Александр Васильев
Java-разработчик Usetech

Нет, это не вопрос быстродействия. Немного предыстории.

В далёком 2018 году финтех-специалисты поделились инсайдом о том, что в начале десятых доходность электронных биржевых торгов составляла $1 с $1000 вложенных денег. К 2018 году она снизилась до 1 цента с $1000. Очевидно, сейчас прибыль и того меньше. Решение по сделке надо принимать очень быстро, каждая микросекунда — на вес золота. Финтех выбирает Java. Так что с быстродействием у нас, Java-программистов, всё хорошо.

Вопрос «Почему же тогда  Java тормозит?» — больше о фичах.

Например, о банальном String. repeat(X). Трудно себе вообразить, но в далёком 2009 году задача «Как повторить строку?» на StackOverflow собрала 720 лайков и имеет несколько оригинальных решений. Точку поставила Java 11, добавив желанный метод в Стандартную библиотеку.

Ну, как поставила? В начале 2022 только треть проектов используют 11 версию Java и выше, а остальные эксплуатируют в проде более старые версии JVM, и для них задача не устарела.

«Подумаешь, String. repeat(Y), ерунда какая!» — скажете вы. Наверное, вам не попадалась задача реализовать логику String. repeat(Z) на собеседовании.

Вот и с асинхронным программированием в Java тоже не всё хорошо. И это уже не мелочь. Дело в том, что закон Мура больше не работает. В оригинале наблюдение Мура звучит как «количество транзисторов в чипе каждый год удваивается». Так было с 1975 примерно по 2010, а после удвоение закончилось.

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

Больше такие фокусы не проходят. Для увеличения производительности приходится использовать несколько процессоров одновременно. Тут перед нами возникает закон Амдала. Его, наоборот, никто не отменял. Распараллелить работу программы очень трудно. Оказывается, всегда какая-то часть вычислений является существенно последовательной, а какую-то можно выполнять на нескольких ядрах одновременно. От соотношений этих частей сильно зависит ускорение от работы на нескольких процессорах.

Мы вынуждены даже распределять задачи между разными машинами, и тогда неизбежно одна начинает ждать другую. И в локальных, и в глобальных параллельных вычислениях результатов приходится дожидаться асинхронно. Асинхронное программирование — это наше настоящее и как минимум ближайшее будущее. В классической Java это будущее печально. Блокирующее программирование для большого количества потоков упирается в ресурсы. Слова thenApply, thenCombineAsync, exceptionally отзываются ужасом в сердце любого, кто имел дело с неблокирующей Core Java.

Альтернативы Java. Какие они?

Уже давно в экосистеме JDK завелись альтернативные Java языки программирования. Например, появились Scala в 2003 году или Clojure в 2007. Что мы можем сказать про эти языки? Много хорошего. 

По некоторым данным, специалист по Clojure получает в два раза больше, чем Java-программист, аналогично (в полтора раза) — специалист по Scala. Однако рынок труда для этих языков меньше, чем у Java. Ни о какой серьёзной конкуренции с их стороны говорить не приходится. Скорее всего, использование таких языков — нишевое и связано именно с высокой параллельностью.

В 2016 году выпускают 1.0 версию Kotlin. К настоящему моменту, будучи моложе Java, Kotlin получил на рынке труда одну четвертую его доли. В нише Android-приложений Kotlin даже стал стандартом де-факто. Почему так произошло?

Во-первых, у Kotlin имеются специальные библиотеки, облегчающие асинхронное программирование. Это так называемые котлиновские корутины. Они позволяют оперировать чем-то вроде легковесных тредов. Легковесные здесь означает дешёвые в смысле аллокации ресурсов на один экземпляр.

Во-вторых, асинхронные конструкции Kotlin легче сопрягать друг с другом, чем головоломные интерфейсы неблокирующей Java.

В-третьих, библиотека котлиновских корутин реализует концепцию структурной конкурентности.

Термин «Структурная конкурентность» ввёл в оборот Мартин Сустрик, создатель ZeroMQ. Вслед за ним Натаниэль Смит великолепно раскрыл концепцию в своих «Заметках о структурной конкурентности». На данный момент имеется несколько реализаций этого подхода для самых разных языков программирования. Среди них libdill (С), Trio (Python), async_nursery (Rust), Venice (Swift) и, конечно же, котлиновские корутины.

Принцип структурной конкурентности позволяет корректно обрабатывать ошибки. В частности, освобождать ограниченные ресурсы: закрывать файловые дескрипторы, сокеты и так далее. Мы же не хотим, чтобы приложения в мобильном устройстве “съедали” в процессе работы память, ресурсы и окончательно “тормозили” устройство? Вот и Google этого не хочет. Так Kotlin получил первенство над Java на Андроиде.

Немного о проекте Loom

Возвращаемся к Java и теме легковесных тредов. В язык планируется добавить фичу, которая сейчас только разрабатывается. Это проект Loom, первый публичный релиз которого состоялся ещё в 2018. Но проект пролетел и мимо 17-го LTS релиза JDK, и мимо проходного 18-го релиза. Судя по тому, в каком темпе прямо сейчас в репозиторий проекта накидывают новые коммиты, Loom может не оказаться и в 19-ом релизе.

Это означает, что котлиновские корутины получили фору, чтобы закрепить своё положение на рынке и стать стандартом де-факто и в этой области. Кому были нужны легковесные треды, тот уже выбрал Kotlin.

Есть ещё один негативный для Loom фактор: на первом этапе в нем не планируют внедрять структурную конкурентность. То есть битва за Android не входит в планы языкостроителей Java.

Второй фактор, который мешает построению распределённых систем на базе Loom — отсутствие на старте реактивных библиотек, которые поддерживали бы концепцию обратного давления.

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

Примерами реализации реактивного взаимодействия служат Project React и Kotlin Coroutines. Фреймворк Spring использует их взаимозаменяемо. Понадобится какое-то время, чтобы создать аналогичную библиотеку под Loom. И это ещё больше задержит внедрение новой фичи.

Будет уместно привести слова Джеймса Гослинга, создателя Java, сказанных вне публичной части интервью 2020 года: «Вначале мы опасались C#, но когда поняли, что всё, что он может — это копировать Java, наши опасения рассеялись». Что же, C# сильно вырос с момента своего появления. Количество его пользователей уже сравнимо с Java-аудиторией, а зарплаты и удовлетворение от использования языка как будто выше.

Уже десять лет как в C# добавлены конструкции async / await, которые открывают возможность писать вменяемый асинхронный код. Разве что по количеству артефактов всевозможных открытых библиотек (в 6 раз) разработка Майкрософт существенно уступает детищу Гослинга. И в более широком смысле — всей экосистеме, потому что библиотеками можно пользоваться из Closure, Scala, Kotlin и прочих JVM-языков.

Подводя итог

Значение поддержки обратной совместимости для развития языка невозможно переоценить. При этом складывается впечатление, что разработчики языка замахиваются на большее. Мы видим, как в язык обратно совместимым способом добавляют новую семантику.

Если взять конкретный Apache Tomcat и, не меняя исходный код, собрать его с помощью прототипа Loom, то веб-сервер покажет небывалую производительность. Кажется, обратная совместимость уже становится фетишем. Она замедляет и развитие языка, и экосистемы в целом. Не стоит Java тормозить настолько сильно.