Java 21에서는 Virtual Threads라는 중요한 기능이 도입되었습니다. 이 기능은 특히 동시성 프로그래밍을 단순화하고 성능을 크게 향상시킬 수 있습니다. 아래에 Virtual Threads를 사용하는 예시와 함께 이 기능의 개념과 장점을 설명하겠습니다.
Virtual Threads 개념
Virtual Threads는 Java의 기존 쓰레드 모델을 확장하여, 운영체제의 물리적 쓰레드와는 별개로 JVM 레벨에서 관리되는 경량 쓰레드입니다. 이는 수천에서 수백만 개의 쓰레드를 효율적으로 생성하고 관리할 수 있게 합니다.
주요 기능:
- 메모리 사용량 감소: 가상 스레드는 약 10KB의 메모리를 사용하며, 기존 스레드는 2MB를 사용합니다.
- 컨텍스트 스위칭 비용 감소: 가상 스레드는 컨텍스트 스위칭 비용이 적어 높은 동시성 환경에서 성능이 향상됩니다.
- 쉬운 적용: 기존 Java 코드에 최소한의 변경으로 통합할 수 있습니다.
Virtual Threads 사용 예시
아래는 Java 21에서 Virtual Threads를 사용하는 간단한 예시입니다.
public class VirtualThreadExample {
public static void main(String[] args) {
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10).forEach(i ->
executor.submit(() -> {
System.out.println("Task " + i + " running on " + Thread.currentThread());
Thread.sleep(1000); // 작업 시뮬레이션
return null;
})
);
}
}
}
Java 21의 Virtual Threads는 동시성 프로그래밍을 보다 간단하고 효율적으로 만들어 줍니다. 이는 특히 많은 수의 I/O 작업을 처리하거나 고도로 동시성이 요구되는 애플리케이션에서 유용합니다. Virtual Threads를 사용하면 기존의 복잡한 비동기 코드 작성 없이도 높은 성능을 유지할 수 있습니다.
Virtual Threads의 내부 구성
1. JVM 내부 스케줄링
Virtual Threads는 JVM에서 관리되는 경량 쓰레드입니다. 운영체제의 물리적 쓰레드와는 별개로 JVM이 자체적으로 스케줄링하고 관리합니다. Virtual Threads는 일반적으로 다음과 같은 방식으로 관리됩니다:
- Carrier Threads: Virtual Threads는 실제로 실행될 때 운영체제의 물리적 쓰레드인 Carrier Threads에서 실행됩니다. Carrier Threads는 제한된 수의 물리적 쓰레드로, Virtual Threads를 실행시키는 역할을 합니다.
- Work Stealing: 작업을 효율적으로 분배하기 위해 Work Stealing 알고리즘을 사용합니다. 이는 비활성화된 Carrier Thread가 다른 Carrier Thread에서 작업을 훔쳐와 실행하는 방식으로, 작업 부하를 균등하게 분배합니다.
2. Task Switching
Virtual Threads는 경량이기 때문에 빠르게 생성되고 컨텍스트 스위칭도 효율적으로 수행됩니다. 일반 쓰레드와 달리 Virtual Threads는 수천 개에서 수백만 개까지 생성할 수 있으며, JVM이 각 쓰레드의 상태를 관리합니다. JVM은 다음과 같은 방식으로 Task Switching을 처리합니다:
- Park/Unpark 메커니즘: Virtual Threads가 블로킹 호출(I/O 작업 등)로 인해 대기 상태가 되면, JVM은 해당 Virtual Thread를 Parking 상태로 전환하고, 다른 Virtual Thread를 실행합니다. 블로킹이 해제되면, JVM은 해당 Virtual Thread를 다시 Unpark하여 실행합니다.
- Continuation: Virtual Threads는 Continuation(연속성)이라는 구조를 사용하여 현재 작업 상태를 저장하고, 필요할 때 언제든지 해당 상태로 복귀할 수 있습니다. 이를 통해 효율적인 컨텍스트 스위칭이 가능합니다.
3. 스택 관리
Virtual Threads는 스택을 효율적으로 관리하기 위해 다음과 같은 기법을 사용합니다:
- Heap Stacks: 일반적인 물리적 쓰레드는 고정 크기의 스택을 사용하지만, Virtual Threads는 힙에 스택을 저장합니다. 이는 필요한 만큼 동적으로 확장 가능하며, 메모리 사용을 최적화합니다.
- Lazy Allocation: Virtual Threads의 스택은 필요한 시점에 할당되며, 사용하지 않는 메모리를 절약할 수 있습니다.
Virtual Threads의 동작 방식
- Virtual Thread 생성: Virtual Thread는 Thread.ofVirtual().start(...)와 같은 메서드를 통해 생성됩니다. 이때 JVM은 최소한의 리소스를 사용하여 Virtual Thread를 생성합니다.
- 작업 스케줄링: Virtual Thread는 Carrier Thread에 의해 스케줄링되어 실행됩니다. Carrier Thread는 Virtual Thread의 작업을 실제로 실행하며, 필요할 때 작업을 다른 Carrier Thread로 전환할 수 있습니다.
- 블로킹 작업 처리: Virtual Thread가 블로킹 호출을 만날 경우, 해당 Virtual Thread는 Parking 상태로 전환됩니다. 이는 물리적 쓰레드가 블로킹되지 않도록 하여, 다른 Virtual Thread가 실행될 수 있도록 합니다.
- 작업 재개: 블로킹 작업이 완료되면, JVM은 해당 Virtual Thread를 Unpark 상태로 전환하여 다시 실행합니다. 이는 Virtual Thread가 계속해서 작업을 수행할 수 있도록 합니다.
Virtual Threads와 기존 쓰레드의 비교
- 메모리 사용량: Virtual Threads는 힙 스택과 Lazy Allocation을 사용하여 메모리 사용량이 최소화됩니다. 반면, 기존 쓰레드는 고정된 크기의 스택을 사용하여 메모리 효율성이 떨어집니다.
- 컨텍스트 스위칭: Virtual Threads는 Continuation을 사용하여 빠른 컨텍스트 스위칭이 가능합니다. 기존 쓰레드는 운영체제 레벨에서의 컨텍스트 스위칭이 발생하여 오버헤드가 큽니다.
- 생성 비용: Virtual Threads는 생성 비용이 낮으며, 수천에서 수백만 개의 쓰레드를 쉽게 생성할 수 있습니다. 기존 쓰레드는 생성 비용이 높고, 많은 수의 쓰레드를 생성할 때 오버헤드가 큽니다.
'JAVA' 카테고리의 다른 글
[JAVA] JVM GC(Garbage Collection)이란 무엇인가? 대표 알고리즘 알아보기! (0) | 2024.05.26 |
---|---|
[JAVA] Java 21 Virtual Thread와 Kotlin Coroutine 비교해보기 (0) | 2024.05.23 |
[JAVA] WebClient 사용법 알아보기! 간단 예제 포함! (0) | 2024.05.17 |
[JAVA] RestTemplate 개념 알아보기! 간단 예제 포함! (0) | 2024.05.16 |
자바 성능 튜닝 정보 알아보기! (0) | 2024.01.30 |