[Java] Virtual Thread

[Java] Virtual Thread

우아한 테크 세미나 [Java의 미래, Virtual Thread]를 보고 정리한 내용입니다.
  • 2018년 Project Loom으로 시작된 경량 스레드 모델
  • 2023년 JDK 21에 정식 feature로 포함

장점

1. 스레드 생성 및 스케줄링 비용이 기존 스레드 보다 저렴

  • 기존 스레드
    • 기존 자바 스레드는 생성 비용이 크다
      • 스레드 풀: 자바의 스레드 생성과 스케줄링 비용이 크기 때문에 미리 여러 개의 스레드를 만들어 놓고 요청이 올 때마다 스레드를 사용
    • 사용 메모리 크기가 크다 - 최대 2MB까지
    • OS에 의해 스케줄링
      • 스레드 생성/소멸시 항상 OS와 통신해야 하기 때문에 System Call 발생 ⇒ 시스템콜 오버헤드 발생
  • virtual thread
    • 생성 비용이 작다
      • 스레드 풀 개념이 존재하지 않고, 항상 새로 생성
    • 사용 메모리 크기가 작다 - 수십 kb까지만
    • OS가 아닌 JVM 내 스케줄링 → 시스템콜에 의한 오버헤드가 발생하지 않음 
  Thread VirtualThread
메모리 사이즈 ~2MB ~50KB
생성 시간 ~1ms ~10us
텍스트 스위칭 시간 ~100us ~10us

 

 

2. 스레드 스케줄링을 통해 Nonblocking I/O 지원

 

  • I/O Blocking
    • 복잡한 비즈니스 로직에 따른 증가하는 I/O blocking time
    • Thread per request 모델에서는 특히 병목이 되는 blocking time
  • Non blocking I/O
    • Spring WebFlux는 netty의 event loop를 이용해 blocking time을 획기적으로 줄임
    • virtual thread인 경우 다음을 활용하여 Non blocking I/O를 가능하게 함
      • JVM 스레드 스케줄링
      • Continuation 활용
    • JVM 스레드 스케줄링
    • Continuation 활용

3. 기존 스레드를 상속하여 코드 호환

final class VirtualThread extends BaseVirtualThread {
abstract sealed class BaseVirtualThread extends Thread
        permits VirtualThread, ThreadBuilders.BoundVirtualThread {

=> VirtualThread == Thread

public ExecutorService executorService() {
	return Executors.newVirtualThreadPerTaskExecutor();
}

 

Virtual Thread 동작 원리

일반 스레드 특징

  • 플랫폼 스레드
  • OS에 의해 스케줄링
  • 커널 스레드와 1:1 매핑
  • 작업 단위 Runnable
  • 유저 영역에서 플랫폼스레드 생성 → JNI 커널영역에 요청 → OS에 커널 스레드 생성 및 스케줄링

Virtual Thread

  • 가상 스레드
  • JVM에 의해 스케줄링
  • 캐리어 스레드와 1:N 매핑
  • 작업 단위 Continuation(실행 가능한 작업 흐름으로, 중단 시점부터 다시 시작 가능)
    • 사용 이유
      • Thead는 작업 중단을 위해 커널 스레드를 중단
      • virtual thread는 작업 중단을 위해 continuation yield
      ⇒ 작업이 block 되어도 실제 스레드는 중단되지 않고 다른 작업 처리
      ⇒ 커널 스레드 중단이 없으므로 시스템 콜 x → 컨텍스트 스위칭 비용이 낮음
  • 동작 과정
    • 유저 영역에 Virtual Thread 생성
    • ForkJoinPool을 스케줄러로 사용
    • 프로세서 수의 Carrier Thread를 worker thread로 사용
    • ForkJoinPool은 Work Stealing 방식으로 작업을 수행
      • WorkQueue에 task를 담아서 순차적으로 수행
      • 본인의 WorkQueue가 비워있으면 남의 WorkQueue에서 훔쳐옴
  • JVM 스케줄링 이유
    • Thread는 생성 및 스케줄링 시 커널 영역 접근
    • Virtual Thread는 커널 영역 접근 없이 단순 java 객체 생성
    ⇒ Virtual thread는 생성 시 시스템 콜 호출을 하지 않음

 

Virtual Thread 주의 사항

Blocking carrier thread(Pin)

  • 캐리어 스레드를 block 하면 virtual thread 활용 불가
    • synchronized
    • parallelStream
  • VM Option 으로 감지 가능

⇒ ReentrantLock으로 바꾸면 가능

  • Spring Boot 3.2, MongoDB는 지원
  • MySQL에서는 지원하지 않아 pin이슈가 많이 발생
  • 병목 가능성이 존재하므로 사용 라이브러리 release 점검
  • 변경 가능하다면 java.util의 ReentrantLock을 사용하도록 변경

No Pooling

  • 생성 비용이 저렴
  • 사용할 때마다 생성
  • 사용 완료 후 GC

CPU bound task

  • 결국 Carrier Thread 위에서 동작하므로 성능 낭비
  • nonblocking의 장점을 활용하지 못함

경량 스레드

  • 수백 만개의 스레드 생성 컨셉이므로 Thread local을 최대한 가볍게 유지해야 함 => 쉽게 생성 및 소멸
  • JDK 21 preview ScopedValue → 스레드 개념을 대체하는 개념

배압

  • Virtual Thread는 배압 조절 기능이 없음
    • 유한 리소스의 경우 배압을 조절하도록 설정이 필요 (DB 커넥션, 파일)
    • 충분한 성능 테스트가 필요
  • Virtual Thread는 가볍고, 빠르고, nonblocking인 경량 스레드
  • Virtual Thread는 JVM 스케줄링 + Continuation
  • Thread per request 사용 중이고, IO blocking time이 주된 병목인 경우 고려
  • Reactive와 Kotline coroutine의 러닝 커브로 부담되는 경우, 쉽게 적용 가능

'BackEnd > Java' 카테고리의 다른 글

[Java] Java 21 특징  (3) 2024.07.15
[Java] Java 17 특징  (0) 2024.06.24
[Java] Java 11 특징  (0) 2024.06.21
[Java] Java 8 특징  (0) 2024.06.21