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

Что такое виртуальный поток?

2.4 Senior🔥 71 комментариев
#JVM и управление памятью#Многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Ответ: Виртуальные потоки в Java

Виртуальные потоки (Virtual Threads) - это революционная особенность Java 19+, которая позволяет легко писать масштабируемые сетевые приложения с миллионами параллельных задач. Это реализация обеспечиваемого потокам (Project Loom).

Основной концепт

// ДО (Java 18 и ранее): платформенные потоки (тяжёлые)
public void oldApproach() {
    // Каждый поток = 1 MB памяти, 1 операция ОС
    // Максимум ~10,000-100,000 потоков на один сервер
    
    for (int i = 0; i < 1000; i++) {
        new Thread(() -> {
            // Обработка
        }).start();
    }
    // Создаём 1000 потоков = 1GB памяти уже!
}

// ТЕПЕРЬ (Java 19+): виртуальные потоки (лёгкие)
public void newApproach() {
    // Каждый виртуальный поток = ~10 KB памяти
    // Можно создать миллионы потоков!
    
    for (int i = 0; i < 1_000_000; i++) {
        Thread.startVirtualThread(() -> {
            // Обработка
        });
    }
    // Создаём 1 миллион потоков = ~10 GB памяти (vs 1 TB раньше!)
}

Архитектура виртуальных потоков

// Виртуальные потоки работают на небольшом числе платформенных потоков

public class VirtualThreadArchitecture {
    // Планировщик (ForkJoinPool)
    // Виртуальные потоки (~1 млн)    Платформенные потоки (~100)
    // ↓ ↓ ↓ ↓ ↓                       ↓ ↓ ↓
    // [VT1] [VT2] [VT3] [VT4] ... → [Worker 1] [Worker 2] ...
    // 
    // Когда виртуальный поток блокируется (I/O, lock),
    // планировщик переводит его в ожидание и запускает другой
}

Синтаксис

// Способ 1: Thread.startVirtualThread()
public void example1() {
    Thread vt = Thread.startVirtualThread(() -> {
        System.out.println("Hello from virtual thread");
    });
}

// Способ 2: Thread.ofVirtual().start()
public void example2() {
    Thread vt = Thread.ofVirtual()
        .name("my-vt-", 0)  // Имя для отладки
        .start(() -> {
            System.out.println("Hello from virtual thread");
        });
}

// Способ 3: ExecutorService с виртуальными потоками
public void example3() {
    try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
        for (int i = 0; i < 10000; i++) {
            executor.submit(() -> {
                // Обработка
            });
        }
    }
}

// Способ 4: StructuredConcurrency (Java 21+)
public void example4() throws InterruptedException {
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        Subtask<String> task1 = scope.fork(() -> "result1");
        Subtask<String> task2 = scope.fork(() -> "result2");
        
        scope.join();
        String result1 = task1.get();
        String result2 = task2.get();
    }
}

Преимущества виртуальных потоков

// 1. Масштабируемость - простое распаралеливание
@RestController
public class ApiController {
    // Старый подход: нужен thread pool of 200
    // Новый подход: можно создать 10000 одновременных подключений
    
    @GetMapping("/api/data")
    public String getData() {
        // Каждый запрос = 1 виртуальный поток
        // Даже блокирующие операции I/O не блокируют платформенный поток
        return blockingDatabaseQuery();  // Ok!
    }
}

// 2. Простота - не нужны async/await, reactive
public void simpleCode() {
    // Старый путь: сложный reactive код
    return getUserAsync()
        .flatMap(user -> getOrdersAsync(user.id))
        .flatMap(orders -> processAsync(orders))
        .subscribe();
    
    // Новый путь: просто синхронный код!
    User user = getUser();
    List<Order> orders = getOrders(user.id);
    process(orders);
}

// 3. Меньше GC давления
public void gcImprovements() {
    // Платформенные потоки = большие heap stacks (~1MB)
    // Виртуальные потоки = маленькие heap stacks (~10KB)
    // Меньше памяти, меньше GC pause
}

Ограничения и осторожность

// ОСТОРОЖНО 1: Pinned threads (виртуальный поток не может быть выгружен)
public void pinnedThreadExample() {
    Thread vt = Thread.startVirtualThread(() -> {
        synchronized(this) {  // ← Синхронизация = pinned!
            // Виртуальный поток привязывается к платформенному
            // Преимущества виртуальных потоков теряются
        }
    });
    
    // ЛУЧШЕ: использовать ReentrantLock
    Lock lock = new ReentrantLock();
    Thread vt2 = Thread.startVirtualThread(() -> {
        lock.lock();
        try {
            // Виртуальный поток может быть выгружен
        } finally {
            lock.unlock();
        }
    });
}

// ОСТОРОЖНО 2: ThreadLocal (становится дороже с миллионами потоков)
public class ThreadLocalConcern {
    private static final ThreadLocal<Connection> connection = 
        ThreadLocal.withInitial(() -> createConnection());
    
    // С 1 млн виртуальных потоков:
    // 1 млн Connection объектов в памяти (если использовать)
    // Лучше: использовать dependency injection
}

// ОСТОРОЖНО 3: CPU-bound работа
public void cpuBoundIssue() {
    // Виртуальные потоки ОТЛИЧНО для I/O-bound
    // Но для CPU-bound работы используй обычные потоки/ForkJoinPool
    
    // Плохо:
    Thread.startVirtualThread(() -> {
        int result = expensiveCalculation();  // CPU-bound
    });  // Теряются преимущества
    
    // Хорошо:
    ForkJoinPool.commonPool().execute(() -> {
        int result = expensiveCalculation();
    });
}

Практический пример: веб-сервер

// Обработка 10,000 одновременных HTTP запросов
@RestController
public class HighThroughputServer {
    
    @GetMapping("/api/users/{id}")
    public User getUser(@PathVariable Long id) {
        // Каждый запрос = 1 виртуальный поток
        // Даже блокирующие операции работают эффективно
        
        // 1. Блокирующий запрос в БД
        User user = userRepository.findById(id).orElseThrow();
        
        // 2. Блокирующий HTTP запрос
        Profile profile = externalService.getProfile(user.id);
        
        // 3. Блокирующий запрос в кеш
        String cached = redis.get("user:" + id);
        
        // Виртуальный поток может быть выгружен в каждой блокировке
        // Платформенный поток используется для других потоков
        
        return user;
    }
}

Сравнение: Платформенные vs Виртуальные потоки

ПараметрПлатформенныеВиртуальные
Память~1 MB~10 KB
СозданиеМедленноОчень быстро
Переключение контекстаОС (дорого)JVM (дешево)
Макс потоков~10,000Миллионы
I/O-boundТребуется asyncПросто синхронный код
CPU-boundХорошоХорошо
СинхронизацияsynchronizedReentrantLock лучше
ThreadLocalOKМожет быть дорого

Совместимость с существующим кодом

// Виртуальные потоки полностью обратно совместимы
// Весь существующий код работает как есть

public void backwardsCompatibility() {
    // Старый код:
    Thread t = new Thread(() -> {
        System.out.println("Old style");
    });
    t.start();
    
    // На Java 19+:
    // - Это всё ещё работает (платформенный поток)
    // - Но ты можешь переписать на виртуальные потоки
    
    Thread vt = Thread.startVirtualThread(() -> {
        System.out.println("New style");
    });
}

Roadmap

// Java 19: Preview (--enable-preview)
public void java19() {
    Thread.startVirtualThread(() -> {});
}

// Java 21: Standard feature (GA) + StructuredConcurrency
public void java21() {
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        // Structured concurrency
    }
}

// Java 23+: дальнейшие улучшения
public void future() {
    // Масштабируемый связанный список для очередей
    // Лучше ThreadLocal поддержка
    // Дальнейшие оптимизации
}

Вывод: Виртуальные потоки - это революция в написании масштабируемых Java приложений. Они позволяют просто писать синхронный код, который работает как асинхронный благодаря миллионам лёгких потоков. Это особенно мощно для I/O-bound приложений (веб-сервисы, микросервисы, API) и кардинально упрощает код по сравнению с реактивным программированием.