← Назад к вопросам
Привязаны ли виртуальные потоки в платформенным
1.8 Middle🔥 181 комментариев
#Docker, Kubernetes и DevOps#REST API и микросервисы
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Привязаны ли виртуальные потоки в платформенным?
Прямой ответ: НЕТ
Виртуальные потоки (Virtual Threads) НЕ привязаны один-в-один к платформенным потокам (Platform Threads / OS threads). Это главная фишка Java 21+.
История: От платформенных потоков к виртуальным
ДО Java 21 (1:1 модель потоков)
┌─────────────────────────────────────────┐
│ Java Application │
│ ┌────────────────────────────────────┐ │
│ │ Thread 1 Thread 2 Thread 3 │ │
│ │ (Java) (Java) (Java) │ │
│ └────────────────────────────────────┘ │
│ (1:1 映射) │
│ ┌────────────────────────────────────┐ │
│ │ OS Thread 1 OS Thread 2 Thread 3│ │
│ │ (expensive) (expensive) (expensive)│ │
│ └────────────────────────────────────┘ │
└─────────────────────────────────────────┘
ПРОБЛЕМА: Каждый Java Thread = OS Thread = ~2MB памяти
Макс ~10,000 потоков на типичной машине
─────────────────────────────────────────
Java 21+ (M:N модель потоков)
┌─────────────────────────────────────────┐
│ Java Application │
│ ┌────────────────────────────────────┐ │
│ │ VThread VThread VThread VThread │ │
│ │ VThread VThread VThread VThread │ │
│ │ VThread VThread VThread VThread │ │
│ │ (millions of them!) │ │
│ └────────────────────────────────────┘ │
│ (M:N映射 - many to few) │
│ ┌────────────────────────────────────┐ │
│ │ OS Thread (Carrier) OS Thread │ │
│ │ OS Thread OS Thread │ │
│ │ (only CPU count + few extra) │ │
│ └────────────────────────────────────┘ │
└─────────────────────────────────────────┘
ПРЕИМУЩЕСТВА:
- Миллионы virtual threads в одной JVM
- Каждый virtual thread = ~100 байт памяти
- Scheduler автоматически распределяет на OS threads
Как работает Virtual Thread Scheduler
// ✅ Virtual Threads (Java 21+)
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
// Создаём 1 миллион virtual threads
for (int i = 0; i < 1_000_000; i++) {
executor.submit(() -> {
try {
// Долгая операция (I/O, блокировка, sleep)
Thread.sleep(1000);
doWork();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
}
// Работает! В старой Java это вызовет OutOfMemoryError
// ❌ Platform Threads (старый способ)
try (var executor = Executors.newFixedThreadPool(1_000_000)) {
for (int i = 0; i < 1_000_000; i++) {
executor.submit(() -> {
try {
Thread.sleep(1000);
doWork();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
}
// OutOfMemoryError: Unable to create new native thread
Внутренняя архитектура Virtual Threads
JVM ForkJoinPool (Scheduler)
├─ Carrier Thread 1 (OS thread, CPU core 1)
│ ├─ VThread 101 (mounted)
│ ├─ VThread 102 (queued, ready to run)
│ ├─ VThread 103 (queued)
│ └─ VThread 104 (queued)
├─ Carrier Thread 2 (OS thread, CPU core 2)
│ ├─ VThread 201 (mounted)
│ ├─ VThread 202 (waiting in queue, blocking on I/O)
│ └─ ...
└─ Carrier Thread N (OS thread, CPU core N)
└─ VThread N01 (mounted)
Когда VThread "mounted" - выполняется на Carrier Thread
Когда VThread блокируется - демонтируется с Carrier Thread
Попадает в queue и ждёт, когда операция завершится
Потом снова монтируется на какой-то Carrier Thread
Демонтирование Virtual Thread
// ✅ ХОРОШО - Virtual Thread демонтируется при блокировке
public void processRequest(HttpRequest request) {
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> {
// 1. Virtual thread монтирован на Carrier thread
String result = callExternalAPI(); // I/O операция
// 2. Virtual thread БЛОКИРУЕТся, демонтируется от Carrier
// 3. Carrier thread может обработать другие VThreads
// 4. Когда I/O завершится, VThread снова монтируется
// 5. Продолжает выполнение
saveToDatabase(result);
});
}
}
// ❌ ПЛОХО - Platform thread не демонтируется
// (старый способ до Java 21)
executor.submit(() -> {
String result = callExternalAPI(); // OS thread ЗАБЛОКИРОВАН
// Другие задачи НЕ могут использовать этот OS thread
// Нужен новый OS thread для других задач
saveToDatabase(result);
});
Сравнение производительности
// Измеритель: 10,000 HTTP запросов, каждый 500ms
// ❌ Старый способ - ThreadPool с 100 потоков
Executor executor = Executors.newFixedThreadPool(100);
long start = System.currentTimeMillis();
for (int i = 0; i < 10_000; i++) {
executor.submit(() -> callAPI()); // 500ms
}
await completion;
long duration = System.currentTimeMillis() - start;
// Результат: ~50 секунд (10_000 / 100 * 0.5)
// Memory: 100 threads * 2MB = 200MB
// ✅ Новый способ - Virtual Threads (Java 21)
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
long start = System.currentTimeMillis();
for (int i = 0; i < 10_000; i++) {
executor.submit(() -> callAPI()); // 500ms
}
await completion;
long duration = System.currentTimeMillis() - start;
// Результат: ~0.5 секунд (все параллельно!)
// Memory: 10_000 threads * 100 bytes = ~1MB
}
Когда Virtual Thread демонтируется?
// 1. I/O операции - ДЕМОНТИРУЕТСЯ
virtualThread(() -> {
Socket.read(); // ДЕМОНТИРУЕТСЯ пока ждёт данных
BufferedReader.readLine(); // ДЕМОНТИРУЕТСЯ
HttpClient.send(); // ДЕМОНТИРУЕТСЯ
System.out.println("back"); // МОНТИРУЕТСЯ снова
});
// 2. Thread.sleep() - ДЕМОНТИРУЕТСЯ
virtualThread(() -> {
System.out.println("before");
Thread.sleep(1000); // ДЕМОНТИРУЕТСЯ (не требует OS thread)
System.out.println("after"); // МОНТИРУЕТСЯ
});
// 3. Блокирующие операции - ⚠️ ЗАВИСИТ
// Может демонтироваться, может остаться монтирован
virtualThread(() -> {
synchronized (lock) { // Может демонтироваться
doWork();
}
lock.lock(); // ReentrantLock демонтируется если сделать right
try {
doWork();
} finally {
lock.unlock();
}
});
// 4. CPU-bound операции - НЕ демонтируется
virtualThread(() -> {
while (true) {
compute(); // Работает на Carrier thread
// Другие VThreads ждут завершения
// Это НЕ эффективно для CPU-bound задач
}
});
Pinning (закрепление) Virtual Thread
// ❌ ПЛОХО - Virtual Thread "закреплен" на Carrier thread
virtualThread(() -> {
synchronized (lock) { // Блокирующий монитор
doWork(); // Virtual thread остаётся монтирован
// Другие VThreads ждут освобождения Carrier thread
}
});
// ✅ ХОРОШО - Virtual Thread может демонтироваться
virtualThread(() -> {
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
doWork(); // Virtual thread может демонтироваться
} finally {
lock.unlock();
}
});
// Способ проверить pinning:
// JVM флаг: -Djdk.tracePinnedThreads=full
// Показывает где Virtual threads закреплены на Carrier
Практический пример: Web Server
// ❌ Старый способ - ограничено 200 потоками
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
server.setExecutor(Executors.newFixedThreadPool(200)); // MAX 200 concurrent requests
server.createContext("/", exchange -> {
String result = slowDatabaseQuery(); // Если долгий - блокирует thread
exchange.sendResponseHeaders(200, result.length());
exchange.getResponseBody().write(result.getBytes());
exchange.close();
});
server.start();
// ✅ Новый способ - Virtual Threads
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
server.setExecutor(Executors.newVirtualThreadPerTaskExecutor()); // Unlimited!
server.createContext("/", exchange -> {
String result = slowDatabaseQuery(); // Virtual thread демонтируется
exchange.sendResponseHeaders(200, result.length());
exchange.getResponseBody().write(result.getBytes());
exchange.close();
});
server.start();
// Теперь можем обработать 1 миллион concurrent requests!
Резюме
На вопрос "Привязаны ли Virtual Threads к Platform Threads?"
ОТВЕТ: НЕТ, используется M:N модель
- Многие Virtual Threads (М) работают на немногих OS Threads/Carrier Threads (N)
- Каждый Virtual Thread может быть демонтирован и переместин на другой Carrier Thread
- При блокировке на I/O Virtual Thread демонтируется (освобождает Carrier Thread)
- OS Thread может одновременно обслуживать множество Virtual Threads
- Это позволяет создать миллионы Virtual Threads вместо тысяч Platform Threads
Virtual Threads = M:N threading (многие к немногим) Platform Threads = 1:1 threading (один к одному)