← Назад к вопросам

Привязаны ли виртуальные потоки в платформенным

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 (один к одному)

Привязаны ли виртуальные потоки в платформенным | PrepBro