В чем разница виртуальных потоков в Java 21 и корутин в Kotlin
Что проверяют
Понимаете ли вы, что обе технологии решают одну задачу, дешёвое ожидание ввода-вывода без блокировки системного потока, но устроены по-разному. Следом обычно спрашивают, что выбрать для нового сервиса на Spring Boot и нужны ли корутины, если уже есть виртуальные потоки.
Как отвечать
Начните с устройства, из него следует всё остальное:
- виртуальные потоки: реализация
Threadвнутри JVM, финальная в Java 21 (JEP 444). Создаются черезThread.ofVirtual().start(...)илиExecutors.newVirtualThreadPerTaskExecutor(). JVM монтирует их на пул потоков-носителей (ForkJoinPoolпо числу ядер), а на блокирующем вызове снимает, сохраняя стек в куче. Модель stackful: JDBC иThread.sleepработают без переписывания кода; - корутины: механизм языка и библиотеки
kotlinx.coroutines. Компилятор превращаетsuspend-функцию в машину состояний с параметромContinuation, модель stackless. Приостановиться можно только в вызове другойsuspend-функции, поэтому код «окрашивается»; - параллелизм: есть у обоих. Корутины на
Dispatchers.Defaultисполняются на нескольких потоках одновременно; - отмена и структура: в Kotlin из коробки есть
CoroutineScope, иерархияJobи кооперативная отмена. В JavaStructuredTaskScopeдолго оставался в preview, отмена идёт черезinterrupt; - экосистема: у корутин есть
Flow, каналы и Kotlin Multiplatform; виртуальные потоки есть только на JVM, зато их сразу получает любой Java-код.
Итог: для Java-сервиса с блокирующим стеком виртуальные потоки дают масштабирование почти даром, корутины сильнее там, где нужны композиция асинхронных операций и отмена. Их можно совместить через newVirtualThreadPerTaskExecutor().asCoroutineDispatcher().
Где спотыкаются
Говорят, что виртуальные потоки переключает операционная система. Это делает JVM, ОС видит только носители.
Не знают про pinning: в Java 21 виртуальный поток внутри synchronized держит носитель. В JDK 24 это исправили (JEP 491), но на Java 21 это частая причина просадок.
Ждут ускорения вычислений. Виртуальные потоки помогают только задачам, которые ждут ввода-вывода, и складывать их в пул как обычные потоки не нужно.