← Назад к вопросам
Что такое виртуальный поток?
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 | Хорошо | Хорошо |
| Синхронизация | synchronized | ReentrantLock лучше |
| ThreadLocal | OK | Может быть дорого |
Совместимость с существующим кодом
// Виртуальные потоки полностью обратно совместимы
// Весь существующий код работает как есть
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) и кардинально упрощает код по сравнению с реактивным программированием.