← Назад к вопросам
Какая связь между потоком оси и легковесным потоком?
2.0 Middle🔥 191 комментариев
#JVM и управление памятью#Многопоточность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Какая связь между потоком ОС и легковесным потоком?
Определения
Поток ОС (OS Thread / Native Thread)
- Создаётся операционной системой
- Имеет собственный стек вызовов
- Требует переключение контекста ОС
- Тяжелый (дорогой) в создании и управлении
- Может выполняться параллельно на разных ядрах процессора
Легковесный поток (Virtual Thread / User-Level Thread)
- Создаётся самой JVM или приложением
- Множество легковесных потоков работают на одном потоке ОС
- Нет переключения контекста ОС
- Дешевый в создании и управлении
- Кооперативная многозадачность (выход по явному условию)
Связь между ними
Поток ОС (тяжелый)
│
├─ Легковесный поток 1
├─ Легковесный поток 2
├─ Легковесный поток 3
└─ Легковесный поток 4
Один поток ОС может управлять несколькими легковесными потоками
Java Traditional Threads (Java 1-20)
// ❌ Старый подход — каждый Thread = один поток ОС
public void traditionalThreads() {
// 1000 потоков = 1000 потоков ОС (дорого!)
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
blockingOperation(); // Блокирующая операция
}).start();
}
}
// Проблемы:
// 1. Каждый Thread требует ~1MB памяти
// 2. 1000 потоков = 1GB памяти только на стеки
// 3. Переключение контекста ОС очень дорого
// 4. Максимум ~10,000 потоков на типичной машине
Java Virtual Threads (Java 19+, Preview в 21, Released в 23)
// ✅ Новый подход — Virtual Threads
public void virtualThreads() {
// 1,000,000 виртуальных потоков на фоне нескольких потоков ОС
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
for (int i = 0; i < 1_000_000; i++) {
executor.submit(() -> {
blockingOperation(); // Легальная блокировка
});
}
}
// Преимущества:
// 1. Каждый Virtual Thread требует ~100 байт памяти
// 2. 1,000,000 потоков = ~100MB памяти
// 3. Нет переключения контекста ОС
// 4. Можно создать миллионы потоков
Архитектура Virtual Threads в Java
┌─────────────────────────────────────────────┐
│ Java Application │
├─────────────────────────────────────────────┤
│ Virtual Thread 1 │
│ Virtual Thread 2 │
│ Virtual Thread 3 │
│ ... │
│ Virtual Thread 1,000,000 │
├─────────────────────────────────────────────┤
│ ForkJoinPool (Scheduler) │
│ (например, 16 потоков) │
├─────────────────────────────────────────────┤
│ OS Thread 1 │
│ OS Thread 2 │
│ ... │
│ OS Thread 16 │
├─────────────────────────────────────────────┤
│ Operating System Kernel │
└─────────────────────────────────────────────┘
Как работают Virtual Threads
Концепция: Continuation
// Virtual Thread может быть "паркован" (suspended) при:
// - Блокирующем I/O (читка с диска, сети)
// - Ожидании монитора (synchronized)
// - Ожидании блокирующей очереди
public class VirtualThreadExample implements Runnable {
@Override
public void run() {
System.out.println("1. Начало работы");
blockingNetworkCall(); // Virtual Thread паркуется
// Поток ОС освобождается, может обслуживать другой VT
System.out.println("2. После блокирования");
System.out.println("3. Конец работы");
}
private void blockingNetworkCall() {
// Это прозрачное блокирование из точки зрения VT
// Но для потока ОС это парковка, а не настоящая блокировка
}
}
Создание Virtual Threads
Вариант 1: Явное создание
// Java 19+ (preview)
Thread vt = Thread.ofVirtual()
.name("virtual-thread-1")
.start(() -> {
System.out.println("Virtual thread running");
});
Вариант 2: ExecutorService
// Java 21+
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
for (int i = 0; i < 1_000_000; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("Task " + taskId + " running on " +
Thread.currentThread());
// Результат: "Task 123 running on VirtualThread[#456]/scheduler-1"
});
}
executor.shutdown();
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
Вариант 3: Structured Concurrency (Java 23+)
// Гарантирует завершение всех потоков
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
var subtask1 = scope.fork(() -> compute(1));
var subtask2 = scope.fork(() -> compute(2));
var subtask3 = scope.fork(() -> compute(3));
scope.join(); // Ждёт завершения всех
System.out.println("Result: " +
subtask1.resultNow() +
subtask2.resultNow() +
subtask3.resultNow());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Сравнение производительности
Traditional Threads vs Virtual Threads
public class PerformanceComparison {
public static void main(String[] args) throws Exception {
testTraditionalThreads();
testVirtualThreads();
}
// ❌ Традиционные потоки
static void testTraditionalThreads() {
long startTime = System.currentTimeMillis();
int count = 10_000;
CountDownLatch latch = new CountDownLatch(count);
for (int i = 0; i < count; i++) {
new Thread(() -> {
try {
Thread.sleep(1000); // Имитация работы
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown();
}
}).start();
}
try {
latch.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
long duration = System.currentTimeMillis() - startTime;
System.out.println("Traditional: " + duration + "ms");
// Вывод: ~12000ms (создание потоков + sleep)
}
// ✅ Virtual Threads
static void testVirtualThreads() {
long startTime = System.currentTimeMillis();
int count = 10_000;
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < count; i++) {
executor.submit(() -> {
try {
Thread.sleep(1000); // Прозрачная парковка
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
}
long duration = System.currentTimeMillis() - startTime;
System.out.println("Virtual: " + duration + "ms");
// Вывод: ~1100ms (только sleep, без overhead потоков)
}
}
Когда Virtual Thread паркуется
// 1. Блокирующие I/O операции
SocketInputStream is = socket.getInputStream();
int data = is.read(); // Virtual Thread паркуется здесь
// 2. Ожидание на synchronize
synchronized(lock) {
// Если lock занят, VT паркуется
}
// 3. Ожидание на monitor
wait(); // Virtual Thread паркуется
// 4. Thread.sleep()
Thread.sleep(1000); // Virtual Thread паркуется, поток ОС свободен
// 5. Lock.lock() из java.util.concurrent
lock.lock(); // Если lock занят, VT паркуется
Ограничения Virtual Threads
// ❌ Что НЕ работает с Virtual Threads
// 1. Pinned потоки (некоторые операции)
synchronized(lock) { // В Java 19-20 это пинит VT
nativeCall(); // Native code pinит VT
}
// Решение в Java 21+: ReentrantLock вместо synchronized
// 2. ThreadLocal теряют смысл (много потоков)
ThreadLocal<String> tl = new ThreadLocal<>();
tl.set("value");
// На 1 млн потоков может быть проблема с памятью
// 3. Очень интенсивные вычисления без блокировок
for (int i = 0; i < 1_000_000_000; i++) {
// Если нет блокировок, VT не может быть вытеснен
// Используй Thread.yield()
}
Правильное использование
// ✅ Хороший кейс для Virtual Threads: I/O-bound операции
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/users/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
// С Virtual Threads можно безопасно использовать
// блокирующие операции в контроллере
UserDTO user = userService.findById(id); // Блокирует, но VT паркуется
return ResponseEntity.ok(user);
}
}
// ❌ Плохой кейс: CPU-bound операции
@Component
public class ComputeService {
public int heavyComputation(int value) {
// Для этого не нужны Virtual Threads
// Используй ForkJoinPool для параллельных вычислений
int result = value;
for (int i = 0; i < 1_000_000_000; i++) {
result *= 2; // CPU bound, no I/O blocking
}
return result;
}
}
Резюме
Традиционные Threads (Java < 19):
- 1 Java Thread = 1 OS Thread
- ~1MB памяти на thread
- Максимум ~10K-50K threads
- Тяжелое переключение контекста
Virtual Threads (Java 19+):
- 1,000,000+ Java VT на ~16 OS Threads
- ~100 байт памяти на VT
- Легкое парковка/возобновление (continuation)
- Идеальны для I/O-bound приложений (веб-серверы)
Ключевая идея: Virtual Threads позволяют писать простой, синхронный код (без async/await или reactive) при том, что под капотом используется асинхронная архитектура с минимальным overhead.