惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Hacker News - Newest:
Hacker News - Newest: "LLM"
C
Cisco Blogs
L
LINUX DO - 热门话题
S
Schneier on Security
NISL@THU
NISL@THU
T
Threatpost
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Latest news
Latest news
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
量子位
Stack Overflow Blog
Stack Overflow Blog
The GitHub Blog
The GitHub Blog
月光博客
月光博客
Cyberwarzone
Cyberwarzone
B
Blog
G
GRAHAM CLULEY
L
Lohrmann on Cybersecurity
Microsoft Security Blog
Microsoft Security Blog
Vercel News
Vercel News
小众软件
小众软件
M
MIT News - Artificial intelligence
I
InfoQ
aimingoo的专栏
aimingoo的专栏
C
CXSECURITY Database RSS Feed - CXSecurity.com
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
美团技术团队
Google DeepMind News
Google DeepMind News
T
The Blog of Author Tim Ferriss
Help Net Security
Help Net Security
WordPress大学
WordPress大学
V
Vulnerabilities – Threatpost
T
Tenable Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
N
Netflix TechBlog - Medium
MyScale Blog
MyScale Blog
Blog — PlanetScale
Blog — PlanetScale
Y
Y Combinator Blog
Google DeepMind News
Google DeepMind News
D
Docker
MongoDB | Blog
MongoDB | Blog
Forbes - Security
Forbes - Security
H
Hacker News: Front Page
A
About on SuperTechFans
L
LINUX DO - 最新话题
B
Blog RSS Feed
D
DataBreaches.Net
博客园 - 司徒正美
Recorded Future
Recorded Future
G
Google Developers Blog
Hugging Face - Blog
Hugging Face - Blog

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Сравнение моделей конкурентности JVM языков: Треды, Пулы и Structured Concurrency
Растегаев Юрий · 2026-05-13 · via Все публикации подряд на Хабре

Средний

6 мин

15K

Привет, Хабр !

Многие если не все встречались с потоками, пулами потоков и проблемами многопоточности и конкурентности. В JVM языках под капотом одна и таже платформа, но Java, Kotlin, Scala и Clojure по-разному работают с потоками, задачами и ожиданием I/O.

В Java кроме классических потоков появились virtual threads. В Kotlin давно есть корутины. В Scala есть ZIO со своим runtime и fiber’ами. В Clojure есть futures, promises, agents и core.async. Снаружи это всё может выглядеть как разные способы выполнить работу параллельно, но внутри модели сильно отличаются.

Мы сравним эти подходы на простых для backend-разработки вещах: как запускается задача, где используются реальные потоки, а где логические, что происходит при ожидании I/O и кто отвечает за отмену. Заодно будет видно, почему в Java можно спокойно писать блокирующий код, а в Kotlin, Scala и Clojure чаще появляются async, await, flatMap.

В первой части начну с Java-базы: обычный Thread, thread pools, virtual threads из Project Loom и Structured Concurrency. В следующих будут корутины, zio-runtime с fibers и Clojure.


1. Классический Java Thread

Начнём с базы. В Java Thread исторически был довольно прямым отражением потока операционной системы. Не всегда один-в-один в деталях реализации JVM и ОС, но для прикладного программиста модель была примерно такая:

java.lang.Thread -> native/platform thread -> OS scheduler

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

Java: создание обычного потока
Thread thread = new Thread(() -> {
    System.out.println("Working in " + Thread.currentThread());
});

thread.start();
thread.join();

В маленькой программе всё выглядит нормально. В сервере под нагрузкой начинается веселье: тысячи соединений, ожидание БД, внешние HTTP-вызовы, очереди, таймауты. Если на каждый запрос заводить платформенный поток, можно быстро упереться не в CPU, а в память и планировщик.

Тут и появляются пулы.


2. Thread pools: компромисс эпохи дорогих потоков

Идея thread pool простая: потоки дорогие, значит, будем их переиспользовать.

Вместо того чтобы создать 10 000 потоков под 10 000 задач, создаём, например, 16 потоков для условного backend-инстанса на 16 vCPU (virtual CPU) и очередь задач перед ними.

Java: классический ExecutorService
ExecutorService executor = Executors.newFixedThreadPool(16);

Future<User> userFuture = executor.submit(() -> userRepository.loadUser(userId));
Future<List<Order>> ordersFuture = executor.submit(() -> orderClient.loadOrders(userId));

User user = userFuture.get();
List<Order> orders = ordersFuture.get();

executor.shutdown();

Сама идея отличная. Потоки не создаются на каждый чих, перед ними появляется очередь, можно ограничить параллелизм и не пустить в базу больше запросов, чем она переварит. Ну и классика: отдельный pool для CPU-bound, отдельный для blocking I/O, плюс правило на случай, если очередь переполнится.

Но пул потоков быстро превращается в инженерную задачу, а не просто в одну строчку кода.

Сколько потоков надо? 8? 16? 200? Размер очереди ограничивать или пусть будет unbounded? Что делать, если в ForkJoinPool.commonPool() случайно попала блокирующая операция? Почему один сервис завис, хотя CPU свободен? Почему CompletableFuture не выполняется? Почему в thread dump все ждут JDBC?

Знакомо?

CPU-bound и I/O-bound

Для CPU-bound задач логика понятная: если у нас 8 ядер, то 800 потоков не сделают вычисления быстрее. Они сделают больше переключений контекста.

Для I/O-bound задач всё хитрее. Поток может почти всё время ждать: БД, сеть, файловую систему, очередь. Поэтому потоков нужно больше, чем ядер. Но насколько больше? Ответ обычно звучит как “it depends”, и это неприятно, потому что зависит правда от всего.

Пулы потоков не исчезли и после Loom. Просто у них меняется роль. Раньше пул часто был способом экономить потоки. Сейчас он всё чаще нужен как ограничитель: не пустить в БД больше 50 запросов, не положить чужой API, не превратить сервис в генератор таймаутов.


3. Зелёные потоки и virtual threads

Зелёные потоки - это потоки, которыми управляет не операционная система, а runtime языка или виртуальной машины. Проще: много лёгких логических потоков могут жить поверх меньшего числа настоящих OS threads.

В Java зелёные потоки были ещё в ранних версиях платформы, потом Java ушла в native threads. А теперь идея вернулась, но уже в современном виде: Project Loom и virtual threads.

Почему Java ушла от ранних green threads

Короткая версия: ОС научились хорошо планировать native threads, а серверной Java нужно было нормально использовать несколько процессоров и ядер.

Старые green threads были удобны для переносимости, но плохо дружили с настоящим параллелизмом, blocking system calls, native-библиотеками, профайлерами и отладчиками. Для растущих серверных приложений это стало слишком большим ограничением.

Первоисточник по Loom: JEP 444: Virtual Threads.

Virtual threads стали стабильными в Java 21.

Java: virtual thread на одну задачу
Thread.startVirtualThread(() -> {
    var user = userRepository.loadUser(userId);
    var orders = orderClient.loadOrders(userId);
    render(user, orders);
});

На вид это обычный Thread. И это не случайность. Loom специально сохраняет привычную Java-модель: обычные методы, обычные stack traces, обычные блокирующие вызовы. Только поток теперь лёгкий.

На пальцах схема такая:

много virtual threads
        |
        v
меньше carrier/platform threads
        |
        v
потоки ОС

Когда virtual thread делает blocking I/O, JVM может “отмонтировать” его от carrier thread. Carrier освобождается и выполняет другую работу. Когда I/O завершится, virtual thread продолжит выполнение.

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

Java: executor, который создаёт virtual thread на задачу
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    Future<User> user = executor.submit(() -> userRepository.loadUser(userId));
    Future<List<Order>> orders = executor.submit(() -> orderClient.loadOrders(userId));

    return render(user.get(), orders.get());
}

Тут важный сдвиг в голове. Мы больше не обязаны переиспользовать virtual threads. Их нормально создавать под задачу. newVirtualThreadPerTaskExecutor() звучит почти как ересь для человека, воспитанного на newFixedThreadPool, но в этом и смысл.

Где virtual threads хороши

На практике они особенно хорошо ложатся на классический backend:

  • request-per-thread модель;

  • JDBC;

  • blocking HTTP clients;

  • RPC;

  • очереди;

  • много одновременного ожидания.

Это не ускоритель CPU. Если у вас 8 ядер и вы считаете хэши, виртуальный миллион потоков не превратит машину в суперкомпьютер.

Зато если у вас 10 000 запросов в основном ждут сеть и БД, virtual threads возвращают право писать нормальный последовательный код без пирамиды callback’ов и без обязательного reactive-стека.


4. Structured Concurrency

Virtual threads отвечают на вопрос: “как дешево запустить много задач?”

Но есть другой вопрос: “как потом не потерять эти задачи?”

Обычный ExecutorService легко рождает сиротские Future: одна задача упала, вторая продолжает работать, родительский запрос уже отменён, а где-то в фоне ещё летит HTTP-вызов. По thread dump потом можно гадать, кто кого породил.

Structured Concurrency пытается вернуть структуру туда, где раньше была куча ручных submit/get/cancel.

Стартовая JEP-ссылка: JEP 428: Structured Concurrency. Дальше API несколько раз проходил через preview-итерации.

JEP по Structured Concurrency

Virtual threads уже стабильны. А вот structured concurrency в Java ещё проходит preview-стадии, поэтому мелкие детали API пока лучше считать временными. Они уже менялись и ещё могут поменяться.

Java: идея StructuredTaskScope
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    var user = scope.fork(() -> userRepository.loadUser(userId));
    var orders = scope.fork(() -> orderClient.loadOrders(userId));

    scope.join();
    scope.throwIfFailed();

    return render(user.get(), orders.get());
}

Я смотрю на это не как на новый красивый синтаксис, а как на попытку вернуть задачам нормальный жизненный цикл:

  • дочерние задачи живут внутри scope;

  • родитель ждёт их завершения;

  • при ошибке можно отменить остальные;

  • при выходе из блока не остаётся фоновых хвостов;

  • stack trace и диагностика становятся ближе к реальной структуре программы.

Рядом с Loom стоит ещё одна важная штука: JEP 506: Scoped Values. Это способ передавать контекст вниз по вызовам и дочерним задачам. Что-то из мира ThreadLocal, но лучше подходит к миллионам virtual threads и structured concurrency.


Вывод

Классические thread pools появились как ответ на дорогие platform threads. Они помогают переиспользовать потоки, ограничивать параллелизм и контролировать очередь задач, но вместе с этим добавляют отдельный слой настройки: размер пула, размер очереди, разные пулы для CPU-bound и blocking I/O.

Virtual threads меняют эту картину. Их не нужно переиспользовать как обычные потоки, поэтому Java снова позволяет писать последовательный blocking-код, но запускать много независимых задач дешевле. При этом ограничения никуда не исчезают: базу, внешний API или очередь всё равно нужно защищать лимитами.

Structured Concurrency закрывает другой вопрос: не только как запустить задачи, но и как управлять их жизненным циклом. Чтобы дочерние задачи не жили отдельно от родительского запроса, нормально отменялись и не превращались в фоновые хвосты.


Во второй части перейдём к JVM-языкам, которые решают похожие задачи уже иначе: Kotlin coroutines, ZIO runtime и Clojure.


Если вам близки темы разработки, рефакторинга, архитектуры и стартапов, буду рад видеть вас в моём Telegram-канале.


Ссылки