하드웨어를 최적으로 활용하지 못하는 근본적인 원인은 OS 스레드와 자바 스레드가 일대일 대응되기 떄문이다. 즉, 자바 스레드가 실행되는 것은 OS 스레드가 사용중임을 의미한다. 따라서 해결 방안은 자바 런타임에서 OS 스레드와 일대일 대응되지 않는 더 효율적인 스레드를 구현해 사용하는 것이다. 운영체제가 많은 가상 주소 공간을 제한된 양의 물리적 RAM에 매핑하여 메모리가 넉넉한 것처럼 보이게 하는 것처럼, 자바 런타임은 많은 수의 가상 스레드를 적은 수의 OS 스레드에 매핑하여 스레드가 넉넉한 것처럼 보이게 하는 것이다.
풀링은 고가의 리소스를 공유하기 위한 것이다. 하지만 가상 스레드는 라이프사이클 동안 하나의 작업만 실행하도록 설계되었으므로 절대 풀링해서는 안된다. 따라사 풀링 없이 항상 새롭게 생성해주면 된다. 만약 애플리케이션 코드에서 스레드 풀 기반의 ExecutorService를 사용중이라면 가상 스레드 기반의 ExecutorService로 마이그레이션 해야 한다.
var executor = Executors.newFixedThreadPool(10)
var executor = Executors.newVirtualThreadPerTaskExecutor()
스레드 로컬은 현재 스레드의 실행과 연관된 데이터들을 다루는 기법으로 캐싱, 파라미터 숨기기 등 다양한 목적으로 사용되고 있다. 하지만 스레드 로컬은 현실적으로 다음과 같은 문제를 갖고 있고, 이로 인해 메모리 누수나 메모리 에러 등이 발생할 수 있다.
따라서 가상 스레드로 전환하고자 한다면 무거운 객체를 스레드 로컬에 저장하지 않도록 해야 한다.
public static void main(String[] args) throws InterruptedException {
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 1000; i++) {
executor.submit(() -> {
// 가상 스레드에서 실행할 코드
System.out.println(Thread.currentThread());
});
}
}
}
spring:
threads:
virtual:
enabled: true
단순히 blocking I/O를 수행하는 역할을위해 5초간 sleep 후 가상 스레드 여부와 스레드 정보를 응답하는 API
@GetMapping
public ResponseEntity<ThreadInfo> getThreadInfo() throws InterruptedException {
Thread.sleep(5000);
return ResponseEntity.ok(
new ThreadInfo(
Thread.currentThread().isVirtual(),
Thread.currentThread().toString())
);
}
application.use-virtual=true 인 경우에 /thread 호출은 다음과 같이 응답하여 가상 스레드가 사용됨을 확인할 수 있다.
{
"isVirtual": true,
"threadName": "VirtualThread[#57]/runnable@ForkJoinPool-1-worker-2"
}
application.use-virtual=false인 경우에 /thread 호출은 다음과 같이 응답하여 플랫폼 스레드가 사용됨을 확인할 수 있다.
{
"isVirtual": false,
"threadName": "Thread[#45,http-nio-8080-exec-2,5,main]"
}
jmeter를 통해서 가상 스레드와 플랫폼 스레드의 처리량을 비교
1000개의 스레드를 생성하여 각 스레드가 100초간 반복 호출하도록 설정
가상 스레드의 경우 초당 181.7건의 처리량을 보였다.
가상 스레드의 경우 모든 응답 시간은 균일하게 5초가 소요되었다.
제약 없이 스레드를 생성하여 처리할 수 있기 때문에 blocking I/O 작업 동안 다른 스레드의 대기가 발생하지 않기 때문이다.
플랫폼 스레드의 경우에는 별도의 thread pool 개수 설정을 하지 않았으므로 디폴트 값이 200개로 수행된다.
플랫폼 스레드의 경우에는 초당 39.3건의 처리량을 보였다.
200개의 스레드로 제한되어 있기 때문에 blocking I/O 작업 동안 다른 스레드에서의 대기가 발생하기 때문이다.
JDK 21의 가상 스레드는 자바 애플리케이션의 성능과 확장성을 크게 향상시킬 수 있는 중요한 기능이다. 기존 스레드 모델의 한계를 극복하고, 대규모 동시성 작업을 보다 효율적으로 처리할 수 있게 해준다.
참고
https://mangkyu.tistory.com/309
https://devel-repository.tistory.com/71