Java의 JDK 21에 새롭게 도입된 Virtual Thread는 기존의 커널 스레드(Kernel-Level Thread, KLT)와 사용자 스레드(User-Level Thread, ULT) 모델을 개선한 기능입니다. 이 글에서는 기존 스레드 모델과 Virtual Thread의 차이점, 그리고 이를 통해 얻을 수 있는 장점을 초보자도 쉽게 이해할 수 있도록 정리해 보겠습니다.
기존 Java 스레드 모델 이해하기
기존 Java의 스레드 모델은 주로 KLT와 1:1로 매핑되어 운영되었습니다. 이 모델에서 Java는 각 스레드가 운영체제의 커널에 의해 관리되었고, 멀티코어 CPU의 자원을 효율적으로 활용할 수 있었습니다. Java의 java.lang.Thread는 결국 커널 스레드로 실행되며, JVM은 이를 JNI(Java Native Interface)를 통해 호출합니다.
이 구조에서 각 스레드는 운영체제가 직접 관리합니다. 스레드가 많아질수록 운영체제는 모든 스레드를 효율적으로 스케줄링해야 하기 때문에 컨텍스트 스위칭 비용이 커지며, 이는 시스템 성능에 부정적인 영향을 미칠 수 있습니다. 특히 웹 서버와 같이 많은 요청을 동시에 처리해야 하는 환경에서는 이러한 비용이 매우 커집니다.
간단한 예제: 기존 스레드 사용하기
다음은 기존 Java 스레드를 사용하는 간단한 예제입니다:
public class SimpleThreadExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("기존 스레드가 실행됩니다.");
});
thread.start();
}
}
위 코드에서 thread.start()를 호출하면 새로운 스레드가 생성되며, 운영체제가 관리하는 커널 스레드가 생성됩니다. 이때 중요한 점은 스레드가 운영체제 커널에 의해 직접 관리된다는 것입니다. 컨텍스트 스위칭이 발생할 때마다 운영체제는 이 스레드들을 모두 관리해야 하므로, 많은 스레드를 사용할수록 성능에 부정적인 영향을 줄 수 있습니다.
기존 스레드의 문제점
기존 스레드 모델은 멀티태스킹 환경에서 다음과 같은 문제점을 가지고 있습니다:
- 높은 컨텍스트 스위칭 비용: 각 스레드는 운영체제에 의해 관리되므로, 스레드 간 전환 시 운영체제는 상태 저장 및 복원을 해야 합니다. 이러한 컨텍스트 스위칭은 비용이 매우 큽니다.
- 제한된 스레드 수: 스레드 수는 시스템의 자원(CPU 및 메모리)에 따라 제한됩니다. 특히 수천 개 이상의 스레드를 생성할 때 메모리 자원의 제약으로 인해 시스템 성능이 저하될 수 있습니다.
- 블로킹 작업의 비효율성: 네트워크 I/O, 파일 I/O와 같은 블로킹 작업을 수행하는 경우, 해당 스레드는 자원을 점유한 채로 블로킹 상태에 놓이게 되며, 이는 시스템의 효율성을 저하시킵니다.
Virtual Thread란 무엇인가?
Virtual Thread는 기존의 스레드 모델을 개선하여, 많은 수의 가상 스레드를 하나의 네이티브 스레드에 할당하여 사용하는 모델입니다. 즉, 기존의 "하나의 사용자 스레드가 하나의 커널 스레드"로 매핑되던 구조에서 "여러 개의 가상 스레드가 하나의 커널 스레드"에 매핑되도록 바꾼 것입니다. 이렇게 함으로써 운영체제의 부담을 덜고, 더 많은 스레드를 생성하고 관리할 수 있게 되었습니다.
Virtual Thread의 주요 특징
- 경량 스레드: 수천 개의 Virtual Thread를 생성하더라도 메모리와 CPU 리소스를 효율적으로 사용할 수 있습니다.
- 컨텍스트 스위칭 비용 감소: 기존 커널 스레드에 비해 가벼운 컨텍스트 스위칭이 가능하므로, 스레드 전환 비용이 매우 적습니다.
- 쉬운 사용법: 기존의 Java 스레드를 사용하는 방식 그대로 Virtual Thread를 사용할 수 있어 기존 코드를 크게 수정하지 않아도 쉽게 도입할 수 있습니다.
- JVM에서 직접 관리: Virtual Thread는 운영체제의 커널 스레드 대신 JVM이 직접 관리하기 때문에, 자원의 효율적인 활용이 가능합니다.
Virtual Thread의 도입으로 인해 기존 Java 스레드의 큰 단점인 높은 컨텍스트 스위칭 비용과 스레드 수의 제약을 해결할 수 있습니다. 이는 대규모 병렬 처리가 필요한 애플리케이션에 큰 장점으로 작용합니다.
Virtual Thread 사용해보기
이제 Virtual Thread를 사용하는 간단한 예제를 보겠습니다. JDK 21에서는 Virtual Thread를 쉽게 생성할 수 있는 API를 제공합니다.
public class VirtualThreadExample {
public static void main(String[] args) {
Thread virtualThread = Thread.ofVirtual().start(() -> {
System.out.println("Virtual Thread가 실행됩니다.");
});
// Virtual Thread가 종료될 때까지 기다립니다.
try {
virtualThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
위 코드에서 Thread.ofVirtual().start()를 사용해 Virtual Thread를 생성하고 실행합니다. 이는 기존의 new Thread()와 사용법이 거의 동일하지만, 내부적으로는 가볍고 효율적으로 동작합니다. Virtual Thread는 JVM의 스케줄러에 의해 관리되므로, 운영체제의 커널 스레드를 직접 생성하지 않고도 다수의 스레드를 쉽게 다룰 수 있습니다.
Virtual Thread를 활용한 블로킹 작업 예제
Virtual Thread는 블로킹 작업에서도 매우 효율적입니다. 다음 예제는 Virtual Thread를 사용해 네트워크 호출을 처리하는 방법을 보여줍니다.
import java.net.HttpURLConnection;
import java.net.URL;
public class VirtualThreadBlockingExample {
public static void main(String[] args) {
Thread virtualThread = Thread.ofVirtual().start(() -> {
try {
URL url = new URL("https://example.com");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
int responseCode = conn.getResponseCode();
System.out.println("응답 코드: " + responseCode);
} catch (Exception e) {
e.printStackTrace();
}
});
try {
virtualThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
이 예제에서 Virtual Thread는 네트워크 요청을 수행하는 동안 블로킹 상태에 놓이지만, 이 블로킹 작업은 Virtual Thread가 운영체제의 커널 스레드에 영향을 미치지 않고도 효율적으로 처리됩니다. 따라서 네트워크 I/O와 같은 작업에서 자원 낭비 없이 더 많은 동시 요청을 처리할 수 있습니다.
Virtual Thread와 기존 스레드의 차이점
- 스레드 수 관리
- 기존 스레드: 기존 스레드는 운영체제의 커널 스레드로 관리되며, 물리적 자원(CPU 및 메모리)에 의존하기 때문에 스레드 수가 제한적입니다. 즉, 시스템에서 생성할 수 있는 스레드 수는 자원의 제약을 받습니다.
- Virtual Thread: Virtual Thread는 수천, 수만 개의 스레드를 가볍게 생성할 수 있으며, JVM의 관리 하에 효율적으로 운영됩니다. 따라서 많은 요청을 동시에 처리하는 서버 애플리케이션에서 매우 유리합니다.
- 컨텍스트 스위칭 비용
- 기존 스레드: 운영체제의 커널 레벨에서 컨텍스트 스위칭이 이루어지므로 비용이 큽니다. 여러 스레드가 번갈아가며 CPU를 사용할 때, 스레드 간의 전환은 많은 자원을 소비합니다.
- Virtual Thread: JVM 내부에서 컨텍스트 스위칭을 수행하여 훨씬 가볍고 빠릅니다. 이는 스레드 전환에 필요한 자원을 크게 절감할 수 있게 합니다.
- 블로킹 처리
- 기존 스레드: Java의 기존 스레드는 블로킹 작업을 수행하면 해당 스레드가 CPU 자원을 차지하면서 기다리게 됩니다. 이는 자원의 낭비로 이어질 수 있습니다.
- Virtual Thread: Virtual Thread는 블로킹 작업이 발생하면 커널 스레드에서 언마운트되고 다른 Virtual Thread가 실행될 수 있도록 스케줄링되므로, 자원 낭비를 줄이고 효율적으로 처리할 수 있습니다.
Virtual Thread를 활용한 예제: Tomcat과 Spring
Virtual Thread는 기존의 Java 프레임워크와도 쉽게 통합될 수 있습니다. 예를 들어, Spring Boot에서 Tomcat의 Executor를 Virtual Thread로 설정하여 더 효율적으로 요청을 처리할 수 있습니다.
import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer;
import org.springframework.context.annotation.Bean;
import java.util.concurrent.Executors;
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
return protocolHandler -> {
protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
};
}
위 코드는 Spring Boot에서 Tomcat 서버의 스레드 풀을 Virtual Thread로 전환하는 방법입니다. 이를 통해 서버는 더 적은 리소스로 더 많은 요청을 처리할 수 있습니다. 특히, 네트워크 I/O와 같은 많은 블로킹 작업을 수행하는 경우, Virtual Thread를 사용하면 성능이 크게 향상될 수 있습니다.
Virtual Thread와 Spring의 통합 효과
- 높은 동시성 처리: Virtual Thread를 사용하면 Tomcat에서 동시성을 크게 향상시킬 수 있습니다. 이는 특히 다수의 클라이언트 요청을 처리해야 하는 웹 애플리케이션에서 유리합니다.
- 블로킹 작업의 최적화: 기존의 스레드 모델에서 블로킹 작업이 많은 경우 전체 시스템의 성능이 저하되었으나, Virtual Thread는 블로킹 작업이 자원 효율에 미치는 영향을 최소화합니다.
Virtual Thread의 한계
Virtual Thread는 많은 장점을 가지고 있지만 모든 상황에서 최선의 선택은 아닙니다. 예를 들어, synchronized 블록을 사용할 경우 Virtual Thread가 커널 스레드에 고정(pinned)될 수 있습니다. 이로 인해 Virtual Thread의 장점을 충분히 활용하지 못하게 됩니다.
- Pinned 상태: Virtual Thread가 synchronized 블록이나 JNI 호출 등으로 인해 커널 스레드에 고정되면, 다른 Virtual Thread가 해당 커널 스레드를 사용할 수 없게 되어 효율이 떨어집니다.
- 비차단 락 권장: 이러한 문제를 해결하기 위해 Java 커뮤니티에서는 synchronized 대신 ReentrantLock과 같은 비차단 락을 사용하는 것이 권장되고 있습니다. synchronized는 Virtual Thread를 고정시켜 효율성을 떨어뜨리기 때문에, Virtual Thread를 도입하려면 이러한 부분을 고려해야 합니다.
정리하며
Virtual Thread는 Java의 멀티스레딩 모델에 큰 혁신을 가져왔습니다. 특히 네트워크 I/O와 같이 많은 블로킹 작업이 발생하는 애플리케이션에서 매우 큰 이점을 제공합니다. 기존의 Spring MVC와 Tomcat 기반의 애플리케이션에서도 Virtual Thread를 도입함으로써 Netty나 WebFlux와 비슷한 성능 향상을 기대할 수 있습니다.
다음은 간단한 요약입니다:
- Virtual Thread는 기존 KLT(1:1)에서 KLT Thread(1) 구조로 개선되었습니다.
- 더 많은 스레드를 더 적은 자원으로 관리할 수 있습니다.
- 블로킹 작업 처리에서 더 효율적입니다.
앞으로 많은 Java 애플리케이션이 Virtual Thread를 활용하여 멀티스레드 처리를 더 간단하고 효율적으로 할 수 있을 것입니다. 레거시 코드를 크게 수정하지 않고도 성능을 향상시킬 수 있는 Virtual Thread, 이제 도입을 고려해보세요!
'개발일지 > 자바' 카테고리의 다른 글
스프링 부트에서 JPA 활용 (0) | 2024.10.29 |
---|---|
네이버 날씨 크롤링해서 현재 지역 날씨 조회하기 (1) | 2023.12.22 |
웹 클롤링 원하는 요소 접근하기 (0) | 2023.12.22 |
Request시 URL 파라미터 아스키코드 디코딩 하는법 (1) | 2023.12.22 |
크롤링해서 이미지 파일 다운받기 (0) | 2023.12.21 |
댓글