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

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

1.7 Middle🔥 141 комментариев
#Другое

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

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

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

Виртуальные потоки

Виртуальные потоки (Virtual Threads) — это легковесные потоки выполнения в Java, представленные в Java 19 (preview) и стабилизированные в Java 21. Они предоставляют альтернативу традиционным платформенным потокам (OS threads), позволяя создавать миллионы виртуальных потоков с минимальными затратами на ресурсы. Виртуальные потоки управляются JVM, а не операционной системой.

Различие между платформенными и виртуальными потоками

Платформенные потоки (Platform Threads):

// Традиционный подход - создание OS threads
public class TraditionalThreadExample {
    public static void main(String[] args) throws InterruptedException {
        // Каждый new Thread() создаёт OS thread (~1-2 MB памяти)
        for (int i = 0; i < 1000; i++) {
            Thread thread = new Thread(() -> {
                System.out.println("Thread " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000); // Блокирует OS thread
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
            thread.start();
        }
        // ~1000 MB памяти только на потоки!
    }
}

Проблемы:

  • Ограничено несколько тысяч потоков (OS limitation)
  • Много памяти на стек каждого потока
  • Context switching накладные расходы
  • Thread pools с ограничением потоков

Виртуальные потоки (Virtual Threads):

// Java 21+ - виртуальные потоки
public class VirtualThreadExample {
    public static void main(String[] args) throws InterruptedException {
        // Каждый virtual thread потребляет ~100 bytes + heap
        for (int i = 0; i < 1_000_000; i++) {
            Thread thread = Thread.ofVirtual().start(() -> {
                System.out.println("Virtual Thread " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000); // Не блокирует OS thread!
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }
        // ~100 MB памяти для 1 миллиона потоков!
    }
}

Создание виртуальных потоков

Способ 1: Thread.ofVirtual() API

// Простой пример
Thread virtualThread = Thread.ofVirtual()
    .name("my-virtual-thread")
    .start(() -> {
        System.out.println("Hello from virtual thread!");
    });

// С именованием
Thread vt = Thread.ofVirtual()
    .name("worker-", 0) // worker-0, worker-1, ...
    .start(() -> {
        // work
    });

virtualThread.join();

Способ 2: ExecutorService с виртуальными потоками

// Java 21+
public class VirtualThreadExecutorExample {
    public static void main(String[] args) throws Exception {
        // Новый factory для виртуальных потоков
        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
            // Каждая задача - отдельный виртуальный поток
            for (int i = 0; i < 10000; i++) {
                final int taskId = i;
                executor.submit(() -> {
                    performTask(taskId);
                });
            }
            // Автоматическое ожидание завершения
        }
    }
    
    private static void performTask(int id) {
        System.out.println("Task " + id + " on " + Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

Способ 3: Thread.Builder

public class ThreadBuilderExample {
    public static void main(String[] args) throws Exception {
        // Создание factory для виртуальных потоков с custom конфигурацией
        ThreadFactory virtualThreadFactory = Thread.ofVirtual()
            .name("app-", 0)
            .factory();
        
        ExecutorService executor = Executors.newThreadPerTaskExecutor(virtualThreadFactory);
        
        for (int i = 0; i < 1000; i++) {
            executor.submit(() -> System.out.println("Task on " + Thread.currentThread()));
        }
        
        executor.shutdown();
        executor.awaitTermination(10, TimeUnit.SECONDS);
    }
}

Архитектура: Carrier Threads

Как работают виртуальные потоки:

// Много виртуальных потоков работают на одном OS thread (Carrier Thread)
// Это называется "continuation" - паузирование и возобновление

public class CarrierThreadExample {
    public static void main(String[] args) throws InterruptedException {
        // 10000 виртуальных потоков могут работать на 8-16 Carrier Threads
        // (зависит от количества CPU cores)
        
        ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
        
        for (int i = 0; i < 10000; i++) {
            final int taskId = i;
            executor.submit(() -> {
                // Виртуальный поток начинает выполнение на carrier thread
                System.out.println("Start task " + taskId);
                
                try {
                    // Когда вызывается blocking operation (sleep, I/O),
                    // виртуальный поток "паузируется" (unmount)
                    // и carrier thread становится свободным для другого virtual thread
                    Thread.sleep(100);
                    // Virtual thread "возобновляется" (mount) на другом carrier thread
                }
                catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                
                System.out.println("End task " + taskId);
            });
        }
        
        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.MINUTES);
    }
}

Практический пример: HTTP клиент с виртуальными потоками

public class VirtualThreadHttpClientExample {
    private static final int REQUESTS = 10000;
    
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();
        
        // Создание виртуального потока на запрос
        ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
        
        long startTime = System.currentTimeMillis();
        
        for (int i = 0; i < REQUESTS; i++) {
            executor.submit(() -> makeRequest(client));
        }
        
        executor.shutdown();
        executor.awaitTermination(5, TimeUnit.MINUTES);
        
        long elapsed = System.currentTimeMillis() - startTime;
        System.out.println("Processed " + REQUESTS + " requests in " + elapsed + "ms");
        System.out.println("Throughput: " + (REQUESTS * 1000 / elapsed) + " requests/sec");
    }
    
    private static void makeRequest(HttpClient client) {
        try {
            HttpRequest request = HttpRequest.newBuilder()
                .uri(new URI("https://api.example.com/data"))
                .GET()
                .build();
            
            HttpResponse<String> response = client.send(request, 
                HttpResponse.BodyHandlers.ofString());
            
            System.out.println("Response: " + response.statusCode());
        } catch (Exception e) {
            System.err.println("Request failed: " + e.getMessage());
        }
    }
}

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

1. Масштабируемость:

// С платформенными потоками: лимит ~1000 потоков
// С виртуальными: миллионы потоков без проблем

public class ScalabilityTest {
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
        
        int numThreads = 1_000_000;
        CountDownLatch latch = new CountDownLatch(numThreads);
        
        long startTime = System.currentTimeMillis();
        
        for (int i = 0; i < numThreads; i++) {
            executor.submit(() -> {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    latch.countDown();
                }
            });
        }
        
        latch.await();
        long elapsed = System.currentTimeMillis() - startTime;
        System.out.println("1 000 000 потоков обработано за " + elapsed + "ms");
    }
}

2. Простота кода (thread-per-request):

// Раньше нужны были thread pools и callback hell
// С виртуальными потоками - простой synchronous код

// ПЛОХО: Callback hell
ExecutorService executor = Executors.newFixedThreadPool(100);
executor.submit(() -> {
    fetchUser(userId, user -> {
        fetchOrders(user.getId(), orders -> {
            fetchOrderDetails(orders, details -> {
                processDetails(details);
            });
        });
    });
});

// ХОРОШО: Virtual threads с простым синхронным кодом
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
executor.submit(() -> {
    User user = fetchUser(userId);           // Блокирует только virtual thread
    List<Order> orders = fetchOrders(user);  // 
    Details details = fetchDetails(orders);  // Очень простой код!
    processDetails(details);
});

Ограничения виртуальных потоков

1. Pinning - когда carrier thread блокируется:

// ПЛОХО: synchronized блокирует carrier thread (pinning)
public class PinningExample {
    private Object lock = new Object();
    
    public void badMethod() {
        synchronized (lock) {  // Pinning! Carrier thread не может переключиться
            // Работа внутри synchronized
        }
    }
    
    // ХОРОШО: используй ReentrantLock или другие альтернативы
    private ReentrantLock better = new ReentrantLock();
    
    public void goodMethod() {
        better.lock();         // Не вызывает pinning
        try {
            // Работа
        } finally {
            better.unlock();
        }
    }
}

2. Native методы и JNI:

// Native методы могут блокировать carrier thread
public class NativeMethodExample {
    public native void nativeMethod();  // Может вызвать pinning
    
    public void virtualThreadTask() {
        nativeMethod();  // Риск: длительное блокирование
    }
}

Best Practices

1. Используй newVirtualThreadPerTaskExecutor() для I/O операций:

public class BestPractices {
    public static void main(String[] args) throws Exception {
        // ✅ Идеально для I/O-bound операций
        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
            for (int i = 0; i < 10000; i++) {
                executor.submit(() -> performIOOperation());
            }
        }
    }
    
    private static void performIOOperation() {
        // HTTP запрос, чтение БД, и т.д.
    }
}

2. Избегай synchronized, используй ReentrantLock:

public class ConcurrentResource {
    private final ReentrantLock lock = new ReentrantLock();
    private int value;
    
    public void increment() {
        lock.lock();
        try {
            value++;
        } finally {
            lock.unlock();
        }
    }
}

3. Профилируй и мониторь pinning:

# Включение мониторинга pinning
java -Djdk.tracePinnedThreads=full MyApp

Заключение

Виртуальные потоки революционизируют способ написания масштабируемых Java приложений. Они позволяют писать простой synchronous код, который работает эффективнее асинхронных альтернатив. Они идеальны для I/O-bound операций и должны стать стандартом для новых приложений Java 21+.