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
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가 기존보다 훨씬 많이 생성되면서 성능에 문제가 발생할 수 있다.