Сначала ответь вслух
Скажи ответ вслух хотя бы пару минут, потом открой эталон и сравни. Эталон открыт всем и не требует входа.
Показать ответ
Эталонный ответ
Виртуальный поток при блокирующем вводе-выводе отпускает поток-носитель, и тот берёт другую задачу. Выигрыш появляется, когда задач тысячи и они в основном ждут.
- Вычисления: носителей столько же, сколько ядер, быстрее не станет.
- Узкий ресурс: пул из 20 соединений к базе остаётся пулом из 20. Число одновременных обращений ограничивают семафором.
- Закрепление: в Java 21 блокировка внутри
synchronizedили нативного кода держит носитель. В Java 24 для synchronized это исправлено (JEP 491). - Большие ThreadLocal-кэши: потоков становится много, и память уходит на копии.
Semaphore db = new Semaphore(20);
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (Request r : requests) {
executor.submit(() -> {
db.acquire();
try { return repository.load(r); } finally { db.release(); }
});
}
}Как у тебя получилось?
Уточняющие вопросы
Так обычно копают глубже. Раскрой строку, когда ответишь вслух.
Уточнение 1-го уровня: Нужно ли складывать виртуальные потоки в пул?
Ответ уровня Middle
Нет: они дешёвые, создаются на задачу. Пул виртуальных потоков ничего не экономит, а ограничение параллельности лучше делать семафором.
Уточнение 2-го уровня: Как найти закрепление носителя?
Ответ уровня Senior
По событию JFR jdk.VirtualThreadPinned: оно показывает стек, где поток заблокировался, удерживая носитель.
Типичные ошибки
- Переводить на виртуальные потоки вычислительные задачи и ждать ускорения.
- Создавать фиксированный пул виртуальных потоков.
Первоисточники
Быстрые задания по теме
Для темы «Виртуальные потоки» быстрых заданий пока нет
Можно проверить всю зону «Многопоточность» тестом на 10 минут.
Проверить себя за 10 минут