Дрессируем потоки. Добиваемся дешёвой конкуренции на JVM. Java Meetup, 24 сентября 2026 г., Сбер

Вячеслав Чернышов

Вячеслав Чернышов

244 views

В 1997 году типичный интернет представлял из себя статичные HTML-страницы, нагрузка на сайты составляла сотни пользователей в день, а передаваемые данные измерялись в килобайтах. Google не существовал, мобильных приложений и стриминга не существовало, а количество аукционов eBay измерялось тысячами. С тех пор требования к высокой нагрузке выросли на порядки, но в разработке по-прежнему доминируют подходы, спроектированные на заре интернета.

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

А они есть. Управляя жизненным циклом потоков, виртуальная машина пытается влиять на другие абстрактные слои приложения (OS, CPU) и сталкивается с навязываемыми ими ограничениями. Взаимное влияние приводит к следующим проблемам:

1. Platform Level: потоки очень дорогие. Для систем с тысячами параллельно исполняемых задач утилизация оперативной памяти может достигать критических значений.
2. OS Level: операционная система ограничивает количество потоков; при превышении новые потоки создаваться не будут и потокозависимые задачи создаваться не будут.
3. Hard Level: вышеперечисленные проблемы выглядят незначительными перед поистине серьёзной проблемой вытесняющей многозадачности и конкурентного доступа к CPU, которая растягивает исполнение потоков по времени.

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

Как быть?

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

Рассмотрим концепцию корутин на разных уровнях: философском, экономическом, психологическом и практическом, — а также, разберём конкретные реализации:

Реализация на языке Java: виртуальные потоки.
Философия виртуальных потоков: мы делаем потоки очень дешёвыми.
Этапы выполнения виртуальных потоков на потоках-носителях.
Как JVM делает блокирующий вызов неблокирующим.
Когда это не работает?
Реализация на языке Kotlin: корутины как suspend-функции.
Почему у корутин не получится как у виртуальных потоков? Компиляция в Java-классы и корутина как конечный автомат.
Как решить проблему блокировки потока-носителя, если JVM про тебя ничего не знает?
Опыт событийных циклов: планировщик асинхронных задач.
Диспетчеры как частичное решение проблемы.
Производительность. Бенчмарки и пруфы собственными руками.

Эмулируем систему с открытым исходным кодом, которая повторит типичное web-приложение с клиентом, бэкендом и внешним интерфейсом ввода-вывода:

На обычных потоках.
На виртуальных потоках.
На корутинах.
Каждый сможет задать необходимые параметры нагрузки, протестировать производительность системы и сделать собственные выводы.

Концепция корутин выходит далеко за рамки философии "очень дешёвые потоки" и за рамки виртуальной машины Java — она по-своему раскрывает дуализм взаимодействия информации и вычислительных ресурсов и реализована во множестве языков. Знакомство с концепцией позволит расширить профессиональный кругозор и посмотреть на проблемы производительности под более широким углом.

Таймкоды:
00:00 – Введение и знакомство с участниками
01:35 – Обзор текущего состояния Spring MVC и Tomcat
03:50 – Концепция thread-per-request и её недостатки
09:00 – Решение проблемы с помощью событийных циклов
12:15 – Эксперимент с Spring WebFlux и Netty
15:10 – Проблемы с блокировкой потоков в событийных циклах
18:15 – Корневая причина проблемы и концепция корутин
34:00 – Вопросы