1. Virtual Thread?

(1) 도입 배경

  1. 기존 자바 스레드인 platform thread 의 한계
    1. platform thread : OS thread 를 래핑한 형태이며, 자바의 전통적인 스레드이다.
    2. (Java thread code 는 실제 OS thread 를 이용하는 방식으로 동작한다.)
    3. OS 커널에서 사용할 수 있는 스레드 갯수가 제한적이고 생성, 유지 비용이 비싸다.
    4. 이를 효율적으로 사용하기 위해 thread pool 사용
  2. 처리량(throughput) 한계
    1. Thread Per Request 으로 사용자 요청을 처리하지만 OS 스레드는 무한정 늘릴 수 없는 단점이다.
    2. application throughput 은 thread pool 에 의존적이다.
  3. Blocking 으로 인한 리소스 낭비
    1. Thread Per Request 를 처리하는 스레드는 I/O 작업 처리시 Blocking 한다.
    2. I/O 작업이 완료될 때까지 다른 요청을 처리하지 못하고 대기해야 한다.
    3. Blocking 방식의 자원 낭비를 줄일 필요성이 있었다.
  4. Reactive Programming 단점
    1. 스레드 자원을 Non-Blocking 방식으로 변경하면서 다른 요청을 처리할 수 있는 방식이다.
    2. Reactive Programming 을 구현하기 위한 추가 학습이 필요해 코드 작성와 이해 비용이 높다.
  5. 스레드 중심의 자바 플랫폼 디자인
    1. 스레드 호출은 thread local 을 사용해 데이터, 컨텍스트를 연결하도록 설계
    2. (+ Exception, Debugger, Profile(JFR) 모두 스레드 기반)
    3. Reactive Programming 은 사용자의 요청이 스레드를 오고 가면서, 컨텍스트 확인이 어려워 디버깅이 어렵다.

[해결하고자 했던 문제]
(1) I/O 작업을 처리시, 스레드 자원을 효율적으로 사용하면서 처리량(throughput) 을 증가시키기
(2) 자바 플랫폼의 디자인과 조화를 이루는 코드 작성이 가능하도록 하기

 

 

(2) Challenge (해결 방안) - Virtual Thread

  1. I/O 작업을 처리시, 스레드 자원을 효율적으로 사용하면서 처리량(throughput) 을 증가시키기
    • Blocking 으로 인한 대기 문제
      • 내부 스케줄링을 통해 플랫폼 스레드가 대기하지 않고 다른 가상 스레드가 작업한다.
      • (Non-blocking 에 대한 처리를 JVM 레벨에서 담당)
  2. 자바 플랫폼의 디자인과 조화를 이루는 코드 작성이 가능하도록 하기
    • 기존 스레드 구조를 그대로 사용하기 때문에 디버깅, 프로파일링등 기존의 도구도 그대로 사용

 

2. Platform, Virtual Thread 구조 차이

(1) Platform Thread

출처 : https://findstar.pe.kr/2023/04/17/java-virtual-threads-1/

  1. 플랫폼 스레드는 실제 OS 스레드를 사용하는 것과 같음
  2. 비용이 비싸기 때문에 스레드 풀을 사용

 

(2) Virtual Thread

출처 : https://findstar.pe.kr/2023/04/17/java-virtual-threads-1/

  1. carrier thread : virtual thread 와 OS thread 를 연결해주는 스레드
  2. mount/unmount : JVM 자체적으로 virtual thread 를 OS 연결하는 과정 (JVM 이 스케줄링)
  3. virtual thread 가 Blocking 이 발생하면 내부 스케줄링을 통해 Carrier Thread 를 다른 virtual thread 의 작업을 처리

 

3. 스프링 부트에서 사용하기

  1. JDK 21 은 2023.09.19 정식 릴리즈
  2. Gradle 버전은 8.4 버전 이상에서 JDK 21을 지원
  3. Spring Boot 3.2 가 2023.11.23 정식 릴리즈되어 JDK 21을 지원

 

(1) 적용방법

  1. spring boot version >= 3.2, spring.threads.virtual.enabled=true 프로퍼티 설정
  2. spring boot version < 3.2, virtual thread executor bean 등록
// Web Request 를 처리하는 Tomcat 이 Virtual Thread를 사용하여 유입된 요청을 처리하도록 한다.
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() 
{
  return protocolHandler -> {
    protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
  };
}

// Async Task에 Virtual Thread 사용
@Bean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
public AsyncTaskExecutor asyncTaskExecutor() {
  return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
}

 

(2) 성능 테스트 결론

  1. Thread Blocking 없는 경우 throughput
    • Platform Thread > Virtual Thread
    • (Virtual Thread Scheduling 을 위한 오버헤드로 예상)
  2. Thread Blocking 이 발생하는 경우 throughput
    • Platform Thread < Virtual Thread
  3. DB Query 에서 Virtual Thread 사용시 SQLTransientConnectionException 발생
    • Connection timeout(30s)이 발생.
    • DB Connection pool 의 한정된 자원의 병목 지점으로 인한 문제
    • → 한정된 자원 접근 제한을 위한 방법 고려 (e.g. semaphore)

 

(3) 주의사항

1. 개별 작업에 virtual thread 할당하는 형태로 변경하기

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
	executor.submit(task1);
	executor.submit(task2);
}

 

2. ThreadLocal 에 값비싼 객체 캐싱 금지

static final ThreadLocal<SimpleDateFormat> cachedFormatter = 
	ThreadLocal.withInitial(SimpleDateFormat::new);
...

cachedFormatter.get().format(...);


=====>>>>>>>

static final DateTimeFormatter formatter = DateTimeFormatter....;
...
formatter.format(...);
  • 플랫폼 스레드의 비용이 비싸 여러 작업 사이에 공유하는 형태로 개발
  • 가상 스레드는 작업당 하나를 활용할 것을 권장되며, 내부 객체를 공유하지 않는다.
  • 가상 스레드가 예상보다 더 많은 메모리를 사용하는 원인이 될 수 있음.

 

3. synchronized keyword 주의

synchronized(lockObj) {
	frequentIO();
}

=====>>>>

lock.lock();
try {
	frequentIO();
} finally {
	lock.unlock();
}
  1. pinning : blocking I/O 작업 수행하는 경우 virtual thread unmount 가 불가능해 Carrier Thread 까지 Blocking 되는 현상 발생 → virtual thread 장점 활용 못함
  2. synchronized가 필요한 경우 자바의 동시성 유틸리티에 있는 명시적인 lock 을 사용하자.

 

Reference