Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Виртуальные потоки
Виртуальные потоки (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+.