Study/Java&Spring

Virtual Thread

kdhoooon 2025. 3. 3. 17:30

등장 배경

  • 기본으로 JVM 에서 제공하는 Thread max = 200 
  • Platform Thread 와 실제 Os thread가 1:1 매핑
  • Context Switching의 부하 문제로 Thread를 무한정으로 사용할 수 없음
  • Platform Thread 와 Virtual Thead는 1:n 관계

 

PureJava Virtual Thread

 

VirtualThread

virtual thread 사용법
실행 화면

ForkJoinPool -> demon Thread

join() 을 사용하는 이유도 demon Thread기 때문에

 

 

thread name 을 등록해서 사용해야 디버깅시에 쉽기 때문에 다음과 같이 factory를 선언하고,

newThreadPerTaskExceutor(ThreadFactor) 를 사용해서 하는것을 추천

 

주의!

Virtual Thread를 ThreadPool 방식으로 사용하면 안된다.

 

 

ForkJoinPool

paraellelStream() 을 사용하면 병렬처리를 한다.

위 결과를 보아 ForkJoinPool은 daemon thread를 사용하고 있다.

 

Pinned Virtual Thread

  • blocking 이 발생하면 platform thread가 unmount 되고 다른 virtual Thread가 platform thread를 사용한다.
  • 내부적으로 synchronized block 가 있거나 native method를 호출할 때는 이러한 동작이 불가능하다.
  • 코드에서 sleep 이 되어있는 동안 platform thread를 다른 virtual thread가 사용하지 못하기 때문에 virtual thread의 장점을 활용할 수 없다.
  • `-Djdk.tracePinnedThreads=full  or -Djdk.tracePinnedThreads=short` 를 이용해서 pinned 되는 부분이 있는지 찾을 수 있다.

  • ReentarantLock syncronized 를 대체할 수 있는 lock을 사용하면 pinned virtual thread가 되지 않을 수 있다.

 

ReentarantLock

syncronized 된 부분을 lock()/unlock() 으로 제어

  • 이런 식으로 제어하면 기존에 pinned virtual thread 되면서 10초 걸리던 코드가 5초에 끝나게 된다.

Virtual Thread Dump

  • 현재는 virtual thread가 몇개 떠있는지 확인할 수 있는 방법이 없다.
  • jcmd <PID> Thread.dump_to_file -format=text <file> 해당 명령어를 통해서 virtual thread가 얼마나 떠있는지 dump 확인할 수 있다.

 

Performance Test

  • 정확히 몇배가 빠르다고는 말할 수 없지만, io bound 테스트에서는 대체로 virtual thread가 빠르다.
  • cpu bound 작업에서는 virtual thread가 더 효율이 좋지 않다.
  • 하나의 어플리케이션에서 사용하는 용도에 맞게 platform thread, virtual thread를 번갈아가면서 사용하면된다.

 

Spring boot 에서 Virtual Thread 사용



  • platform thread가 부족할때는 코드의 시작과 끝에서 사용하는 virtual thread가 다를 수 있다.

 

@Async

  • 기존에는 ThreadPool에 있는 Thread를 사용했는데, Virtual Thread에서는 Virtual Thread를 가져와서 사용하는것을 볼 수 있다.

 

Scheduling

  • 따로 설정할 것이 없이 @EnableScheduling 을 사용하면 사용할 수 있다.

  • thread 이름을 다음과 같이 사용하면 바꿀 수 있다.

 

Scheduler에서 virtual thread / platform thread 를 모두 사용하는 방법

  • 각각의 Bean을 생성해서 사용

  • 이 두개의 Bean을 주입시켜서 사용하면 된다.
  • @Primary를 이용해서 default thread를 정해서 사용하면 된다.

  • 사용하는 쪽에서는 scheduler를 지정해서 사용

 

Executor

  • scheduler와 동일하게 두개의 bean을 생성해서 사용
  • taskExecutor의 이름의 빈이 존재하면 그것이 default로 사용하게 된다.

  • default 가 아닌 executor을 사용하려면 위와 같이 사용하면 된다.

 

 

Bottleneck

  • Virtual Thread로 적용하면 DB connection 과 같은 부수적인 connection pool도 늘려줘야 bottleneck 현상이 발생하지 않는다.
  • Socket 제한이 있을 수 있기때문에 Virtual Thread를 적용할 때에도 확인해 봐야한다.
  • Lock을 사용해서 Thread가 기존보다 훨씬 많이 생성되면서 성능에 문제가 발생할 수 있다.