[Java] ๊ฐ์ ์ค๋ ๋ (Virtual Thread) ํ์ด๋ณด๊ธฐ
Java์ Thread๋ฅผ ๋ณด๋ฉด ๋์ ๊ณ ๋ฏผ์ด์๋ ๋ถ๋ถ์ด ์์์ต๋๋ค. Java์ Thread API๋ ์ด์์ฒด์ ์ ์ค๋ ๋์ 1:1๋ก ๋งคํ๋์ด ์์ด ์ธ ๋๋ง๋ค ์ปจํ ์คํธ ์ค์์นญ์ผ๋ก ์ธํ ๋น์ฉ ๋ฌธ์ ๊ฐ ๊ฑธ๋ฆผ๋์ด๊ณ , ์๋ฒ๋ฅผ ๊ฐ๋ฐํ๋ ์ ์ฅ์์ ๋์ฉ๋ ํธ๋ํฝ์ ๋ฐ์์ ๊ณ ๋ คํด์ผ ํ๋ค๋ฉด ์ค์ผ์ผ ์์์ ํด์ผํ ์ง ๋ฆฌ์กํฐ๋ธ ํ๋ก๊ทธ๋๋ฐ(Reactive Programming)์ ํด์ผํ ์ง ์ ํ์ ๊ณ ๋ฏผ์ ํฉ์ธ์ด๊ฒ ๋ฉ๋๋ค.
๊ฐ๋ฐ์ ์ ์ฅ์์ ๊ฐ์ฅ ์ฝ๊ฒ ์๊ฐํ๋ค๋ฉด ์ค์ผ์ผ ์์์ผ ๊ฒ์ ๋๋ค. ์์๋ ์ฟ ๋ฒ๋คํฐ์ค(k8s)์ ๊ฐ์ ์ปจํ ์ด๋ ์ค์ผ์คํธ๋ ์ด์ ์์คํ ๋ด์ง ํด๋ผ์ฐ๋์์ ์คํ ์ค์ผ์ผ๋ง ๊ธฐ๋ฅ์ ์ ์ง์ํ๊ณ ์์ด ํธ๋ํฝ์ด ๋๋์ผ๋ก ๋ฐ์ํด๋ ์๋์ผ๋ก ์ค์ผ์ผ ์์ํ์ฌ ํธ๋ํฝ ๋ฐ์์ ์กฐ์ ํด์ฃผ๋ ๊ฐ์ฅ ํธํ ๋ฐฉ๋ฒ์ด์ง์. ํ์ง๋ง ์ด๋ ์ฌ์ ํ ๋ด๊ฐ ์ด์ํ๋ ์๋น์ค์ ํ๋์จ์ด๋ฅผ ์ ์ ํ ์ฌ์ฉํ์ง ๋ชปํ๋ค๋ ๋ฌธ์ ๊ฐ ๋จ์ ์๊ฒ ๋ฉ๋๋ค.
๊ทธ๋ฌ๋ฉด ๋ฆฌ์กํฐ๋ธ ํ๋ก๊ทธ๋๋ฐ์ ์ด๋จ๊น? ๋ฆฌ์กํฐ๋ธ ํ๋ก๊ทธ๋๋ฐ์ Thread๊ฐ ๊ฐ์ง ํ ์์ฒญ๋น ํ ์ค๋ ๋(Thread per request) ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ์์๊ธด ํ์ง๋ง ๊ทธ๋ฅผ ์ํด์๋ ์ฌ๋ฌ ๊ฐ์ง ๊ณ ๋ฏผํด์ผ ํ ๋ถ๋ถ์ด ๋ง์ด ์์ต๋๋ค.
- ํจ์ ์ ๋ฌธ์ (Colored Function Problem)
- ๋ณต์กํ ์ฝ๋ ์คํ์ผ
- ๊ธฐ์กด ์คํ์ผ๊ณผ ๋ค๋ฅธ ํ๋ก๊ทธ๋๋ฐ ํ ํฌ๋
ํ ๋ฒ ์ฏค ๋ฆฌ์กํฐ๋ธ ํ๋ก๊ทธ๋๋ฐ์ ํด๋ดค๋ค๋ฉด ์์๊ฒ ์ง๋ง ๋ฆฌ์กํฐ๋ธ ํ๋ก๊ทธ๋๋ฐ์ ํ๋ ค๋ฉด ๊ธฐ์กด์ ์ค๋ ๋ ์คํ์ผ๊ณผ๋ ๋ค๋ฅธ ํ๋ก๊ทธ๋๋ฐ ๋ฌธ๋ฒ์ ์ฌ์ฉํด์ผ ํ๊ณ , ๋ด๊ฐ ์ฌ์ฉํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๋ฆฌ์กํฐ๋ธํ๊ฒ ๋์ํ๋ ํจ์๋ค์ด์ด์ผ ํ๋ฉฐ ๊ทธ๋ ์ง ์์ผ๋ฉด ์ด๋ฅผ ๋ฆฌ์กํฐ๋ธ ์คํ์ผ๋ก ๋ฆฌํฉํ ๋งํ์ฌ ์ฌ์ฉํ๋ ๋ฑ์ ๋ฒ๊ฑฐ๋ก์์ด ๋ง์์ง๋๋ค.
๋ฟ๋ง ์๋๋ผ ๋ฆฌ์กํฐ๋ธ ํ๋ก๊ทธ๋๋ฐ์ ๊ธฐ์กด์ ์๋ฐ ์ค๋ ๋ ์คํ์ผ์ฒ๋ผ ์์ฐจ์ ์ธ ํ๋ก๊ทธ๋๋ฐ ํ ํฌ๋์ ์ฌ์ฉํ์ง ์๊ณ , ์ฒด์ธ์ ์ด์ฉํ์ฌ ์ด๋ฆฌ์ ๋ฆฌ ๋น๋๊ธฐ ์ฒ๋ฆฌํ๊ณ , ๋ณด๋ด๊ณ , ํ๋ ๋ฑ์ ์์ ์ ๋ฐ๋ณตํ๊ฒ ๋๊ธฐ ๋๋ฌธ์ ๋ฆฌ์กํฐ๋ธ ํ๋ก๊ทธ๋๋ฐ์ ์ฒ์ํ๋ค๋ฉด ๊ทธ๋งํผ ๋ฐฐ์์ผํ๋ ๊ธฐ์ ์ด ๋ง์์ง๊ณ ์ฝ๋ ์ ๋ฆฌ๋ ์ด๋ ค์์ง๋๋ค.
Virtual Thread
๊ทธ๋ฌ๋ ์ค ์ ๋ OpenJDK ๋ด์์ ์งํ๋๋ ๋์์ฑ ์ฒ๋ฆฌ ๊ด๋ จ ํ๋ก์ ํธ๋ฅผ ๋ณด๊ฒ ๋์์ต๋๋ค. ์ด๋ฆ์ Project Loom์ด์๊ณ , JDK 19์์ ์ฒ์ ๋ฑ์ฅํ์ฌ ๊ตฌ์กฐ์ ๋์์ฑ, ๊ฐ์ ์ค๋ ๋, ์ง์ ๋ฒ์ ๊ฐ์ด๋ผ๋ ์ธ ๊ฐ์ง ๊ฐ์ ๋ฐฉ์์ ์ ์ํฉ๋๋ค.
Project Loom
๊ธฐ์กด์ Thread API๊ฐ ์ด์์ฒด์ ์ ์ค๋ ๋๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉํ๋ ๊ฒ์ ๋ํด ํ๋ ํ๋ก๊ทธ๋๋ฐ์ ์ผ๋ก ๋ฆฌ์์ค๊ฐ ๋ง์ด ์๋ชจ๋๋ค ํ๋จํ์ฌ ์ต์ ํ ์งํ์ ์ํด ์์๋ Java ๋ด ํ๋ก์ ํธ๋ก Java 21 ๋ฒ์ ์์ ๊ฐ์ ์ค๋ ๋ ๊ตฌํ์ฒด๊ฐ ์ต์ด๋ก ๋ฑ์ฅํจ.
Java 21์ด ๋ฑ์ฅํ ์ง๊ธ, ๊ฐ์ ์ค๋ ๋๋ Preview๊ฐ ์๋ ๊ณต์ ๊ธฐ๋ฅ์ผ๋ก ๋ฑ์ฅํ๊ฒ ๋ฉ๋๋ค. ๊ฐ์ ์ค๋ ๋๋ ์์์ ์ธ๊ธํ๋ค์ํผ ๊ธฐ์กด์ ์ค๋ ๋์ ์ฑ๋ฅ์ ๊ฐ์ ํ๊ธฐ ์ํ ์๋ฐ์ ์๋ก์ด API์ด๋ฉฐ ์ด ๊ธฐ๋ฅ์ ๋ชฉ์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ์์ฒญ๋น ์ค๋ ๋ ํ ๊ฐ๋ฅผ ์ฌ์ฉํ๋ ์๋ฒ ์ ํ๋ฆฌ์ผ์ด์ ์ต์ ํ
- ๊ธฐ์กด Thread API๋ฅผ ์ต์ํ์ ๋ณ๊ฒฝ๋ง์ผ๋ก ๋ง์ด๊ทธ๋ ์ด์
- ๊ธฐ์กด JDK ๋๊ตฌ๋ก ๊ฐ์ ์ค๋ ๋ ๋๋ฒ๊น , ํ๋กํ์ผ๋ง ๊ฐ๋ฅ
์ ๋ชฉ์ ์ JEP425: Virtual Threads ์์ ์ฐธ๊ณ ํ์์ผ๋ฉฐ ์ด๋ฅผ ๋ณด์ ๊ฐ์ ์ค๋ ๋๋ ์์ฒญ ํ ๊ฐ๋น ์ค๋ ๋ ํ๋๋ฅผ ์ฌ์ฉํ๋ ๊ธฐ์กด Thread API๋ฅผ ๋์ฒดํ๊ธฐ ์ํด ๊ณ ์ํ ๊ฒ์ผ๋ก ์ด๋ฒคํธ ๋ฃจํ๋ฅผ ์ฌ์ฉํ ๋น๋๊ธฐ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์ ๋์ํ๊ธฐ ์ํด ๋์จ ๊ฒ์ผ๋ก ๋ณด์ ๋๋ค.
๊ทธ๋ ๋ค๋ฉด ๊ธฐ์กด Thread์ Virtual Thread๋ ์ด๋ ํ ์ฐจ์ด๊ฐ ์๋ ๊ฒ์ผ๊น์?
Virtual Thread vs Platform Thread
Java 21์์๋ ์ค๋ ๋ ๊ฐ๋ ์ด ๋ ๊ฐ์ง ๋ฑ์ฅํ๋ฉด์ ๊ธฐ์กด์ ์ค๋ ๋๋ฅผ ํ๋ซํผ ์ค๋ ๋(Platform Thread), ๊ฐ์ ์ค๋ ๋(Virtual Thread)๋ก ๋ช ๋ช ํ์์ต๋๋ค. ๋ฐ๋ผ์ ์ด ๊ธ์์๋ ๊ฐ์ ์ค๋ ๋์ ํ๋ซํผ ์ค๋ ๋๋ผ๋ ์ฉ์ด๋ก ๊ตฌ๋ถํ์ฌ ์ฌ์ฉํ ๊ฒ์ด๋ฉฐ ๋น๊ต๋ฅผ ์ํด ๋จผ์ ํ๋ซํผ ์ค๋ ๋๊ฐ ์ด๋ป๊ฒ ๋์ํ๋์ง ๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.

ํ๋ซํผ ์ค๋ ๋๋ TaskExecutor ์ธํฐํ์ด์ค๋ฅผ ์ฌ์ฉํ์ฌ ์ค๋ ๋ ํ(Thread Pool)์ ์ฌ์ฉํ์ฌ ์ฌ์ฉํ๋ค๊ณ ๊ฐ์ ํ์ ๋ ํ๋์ ํ์คํฌ๋น ํ๋์ ์ค๋ ๋๋ฅผ ํ ๋นํ์ฌ ์์ ํ๊ฒ ๋ฉ๋๋ค. ์์๋ค์ํผ ํ๋ซํผ ์ค๋ ๋๋ ๊ธฐ์กด์ Thread API๋ก, ์ด์์ฒด์ ์ค๋ ๋๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ๋ง์ ๋น์ฉ์ด ๋ฐ์ํฉ๋๋ค.

๊ฐ์ ์ค๋ ๋์์๋ ์ด๋ฅผ ๊ฐ์ ํ๊ธฐ ์ํด ํ๋ซํผ ์ค๋ ๋๋ฅผ ์บ๋ฆฌ์ด ์ค๋ ๋(Carrier Thread)๋ก ์ ์ํ์ฌ ๋ด๋ถ์ ์ผ๋ก ์ฌ์ฉํ๋ ๋จ์ผ ํ๋ซํผ ์ค๋ ๋์ ๊ทธ ์ฉ์ด๋ฅผ ๋ถ๋ฆฌํ์ต๋๋ค. ๋ฐ๋ผ์ VirtualThreadBuilder ๊ฐ์ฒด๊ฐ ์์ ์ ๋ฐ์์ ๋ ๊ฐ์ ์ค๋ ๋ ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ณ , ํด๋น ์ค๋ ๋๊ฐ ๊ฐ์ง๊ณ ์๋ ์์ ์ ForkJoinPool์ด๋ผ๋ ์บ๋ฆฌ์ด ์ค๋ ๋ ํ์ด ๋ฐ์ ๊ฐ ์บ๋ฆฌ์ด ์ค๋ ๋์ ํ ๋นํ๋ ๋ฐฉ์์ ๋๋ค.
ForkJoinPool
ExecutorService์ฒ๋ผ Thread Pool์ ์์ฑํ์ฌ ์ฌ๋ฌ ์์ ์ ๋ณ๋ ฌ ์ฒ๋ฆฌํด์ฃผ๋๋ก ํ๋ ๊ฐ์ฒด๋ก ExecutorService์๋ ๋ฌ๋ฆฌ, Task ํฌ๊ธฐ์ ๋ฐ๋ผ ๋ถํ (Fork)ํ์ฌ ํด๋น Task์ ๋ง์ง๋ง ๋ถ๋ถ์ด ์ฒ๋ฆฌ๋๋ฉด ์ด๋ฅผ ํฉ์ณ(Join) ๋ฆฌํดํ๋ ๋ถํ ์ ๋ณต ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ๋์ํจ ๊ฐ์ ์ค๋ ๋ ์ค์ผ์ค๋ง์ ์ํด ์ฌ์ฉํ๋ ForkJoinPool์ ๊ธฐ์กด ForkJoinPool๊ณผ ์ฝ๊ฐ ๋ค๋ฅธ ํํ๋ฅผ ์ง๋๊ณ ์์
์ด ๋ ์ค์ํ ๊ฑด ์บ๋ฆฌ์ด ์ค๋ ๋๋ ํ๋ซํผ ์ค๋ ๋์์ I/O Blocking ๋ ์์ ์ ํ์ ์์ ์ฐ์ ์์๋๋ก ์์ ํ๋ค๋ ํน์ง์ ๊ฐ์ง๊ณ ์๋ค๋ ์ ์ ๋๋ค. ์บ๋ฆฌ์ด ์ค๋ ๋๋ณ๋ก ๊ฐ ๋ฆฌ์์ค์ ๋ฐ๋ผ 3~4๊ฐ ์ ๋ ๋ถ์ฌํ์ฌ ํ๋ซํผ ์ค๋ ๋ ์ฌ์ฉ ๋น์จ๊ณผ ์ปจํ ์คํธ ์ค์์นญ ๋น์ฉ์ ๋ฎ์ถ๋ฉด์ ์ฑ๋ฅ์ ๊ฐ์ ํฉ๋๋ค.
๋ง์ ์์ ์ ํ์ ๋ฃ์ ์ ์๊ธฐ ๋๋ฌธ์ ๋ง์ ์ง์ฐ์๊ฐ์ด ๋ฐ์ํ๋ ๊ณณ์์๋ ์คํ๋ ค ํ๋ซํผ ์ค๋ ๋๋ฅผ ์ฌ์ฉํ๋ ๊ฒ๋ณด๋ค ์ฑ๋ฅ์ ์ ํ์ํค๋ ์ญํจ๊ณผ๋ฅผ ๋ด๊ธฐ ๋๋ฌธ์ ์๋์ ์ํฉ์์๋ ์คํ๋ ค ์ข์ง ์์ต๋๋ค.
- ์ฒ๋ฆฌ ์๊ฐ์ด ์ค๋ ๊ฑธ๋ฆฌ๋ ์์ (๋์ฉ๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ)์๋ ํ๋ซํผ ์ค๋ ๋ ์ฌ์ฉ์ด ์ ์
- Java Stream API๋ฅผ ๋์ฒดํ๊ณ ์ ํ๋ ์์ ์ด ์๋๋ฏ๋ก ํด๋น ์ฝ๋๊ฐ ํฌํจ๋ ์์ ์ ํ๋ซํผ ์ค๋ ๋ ์ฌ์ฉ์ด ์ ์
์บ๋ฆฌ์ด ์ค๋ ๋์ ํฌํจ๋ ์์ ํ์ ๋ํด ์ข ๋ ์ค๋ช ํ์๋ฉด ํด๋น ์์ ๋ค์ FIFO ๋ฐฉ์์ผ๋ก ์ค์ผ์ค๋งํ๋ฉฐ JVM ๋ด์์ ๊ฐ์ ์ค๋ ๋์ ๊ฐฏ์๋ ํ๋ซํผ ์ค๋ ๋์ฒ๋ผ ์ ํ์ ๋์ง ์์ต๋๋ค. ๊ทธ ์ด์ ๋ ๊ฐ์ ์ค๋ ๋์ ์์ฑ ๋น์ฉ(1KB)์ด ํ๋ซํผ ์ค๋ ๋ ์์ฑ ๋น์ฉ(1MB)์ ๋นํด ํ์ ํ ๋ฎ๊ธฐ ๋๋ฌธ์ ๋๋ค.
๊ฐ์ ์ค๋ ๋ ์ค์ผ์ค๋ง์ ์ํ ์บ๋ฆฌ์ด ์ค๋ ๋ ๊ฐฏ์ ๊ด๋ฆฌ๋ ์๋์ JVM ํ๋ผ๋ฏธํฐ๋ฅผ ํตํด ๊ด๋ฆฌ๋ฉ๋๋ค.
- jdk.virtualThreadScheduler.parallelism
- jdk.virtualThreadScheduler.maxPoolSize
- jdk.virtualThreadScheduler.minRunnable
parallelism์ ๋์ ์ฒ๋ฆฌ ๊ฐฏ์๋ฅผ ์๋ฏธํ๋ฉฐ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ๊ธฐ๊ธฐ์ ์ต๋ ๊ฐ์ฉ ์ฝ์ด ๊ฐฏ์๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์ฌ๋ฌ ๊ฐ์ ์ค๋ ๋๋ฅผ ๋์์ ์คํํ ์ ์๋ ๊ฐฏ์๋ฅผ ์ ํ๊ฒ ๋ฉ๋๋ค.
maxPoolSize๋ ์ต๋ ์บ๋ฆฌ์ด ์ค๋ ๋ ํ ๊ฐฏ์๋ฅผ ๋งํ๋ฉฐ ๊ธฐ๋ณธ๊ฐ์ max(parallelism, 256)์ ๋๋ค.
minRunnable์ ์ต์ ์คํ ๊ฐ๋ฅํ ๊ฐฏ์๋ฅผ ๋งํ๋ฉฐ ๊ธฐ๋ณธ๊ฐ์ max(parallelism / 2, 1)์ ๋๋ค. ๋ณดํธ์ ์ผ๋ก ์ฝ์ด ๊ฐฏ์์ ์ ๋ฐ ์ ๋๋ฅผ ์ต์ ๋จ์๋ก ์คํํฉ๋๋ค.
Virtual Thread Scheduling
๊ฐ์ ์ค๋ ๋๋ ์ด๋ป๊ฒ I/O blocking ์ํ๋ฅผ ์ ์ดํ๋ ๊ฑธ๊น์? ์ ๊ธ์ ์ดํด๋ดค์ ๋ ์ค์ผ์ค๋ฌ๊ฐ ๊ฐ ์์ ์ ์บ๋ฆฌ์ด ์ค๋ ๋๋ก ์ ๋ฌํ์ฌ ์ํํ๋ค๋ฉด ๊ทธ๋ค์ด I/O blocking ์ํ์ธ์ง๋ฅผ ๊ฐ์ ์ค๋ ๋๋ ์์ ์ ํตํด์ ์ ์ ์์ด์ผ ํ ๊ฒ์ ๋๋ค. ์ด๋ฒ์๋ ๊ฐ์ ์ค๋ ๋๊ฐ ์ด๋ค์์ผ๋ก ์์ ์ ์ ์ดํ๋์ง์ ๋ํด ์์๋ณด๊ฒ ์ต๋๋ค.

๊ฐ์ ์ค๋ ๋๋ ์ฌํ ์ค๋ ๋์ ๋ง์ฐฌ๊ฐ์ง๋ก JVM์ ํ(Heap) ์์ญ์ ์ ์ฅ๋ฉ๋๋ค. ํ์์ญ์ ์ ์ฅ๋์์ ๋๋ ์์ ์ด ์งํ ์ค์ธ ์ํ๋ก ์งํ ์ค์ ์ฌ์ฉํ ํจ์ ํธ์ถ ์ ๋ณด, ๋ณ์ ์ ๋ณด ๋ฑ์ด ์ฌ๊ธฐ์ ๋ด๊ธฐ๊ฒ ๋๊ณ , I/O blocking์ด ๋ฐ์ํ๋ฉด ์ด๋ค์ ์์ ํ๋ ์คํ ํ๋ ์(stack frames)์ ๋ณ๋์ ํ ์์ญ์ ์์๋ก ๋ณต์ฌํด๋๋ค๊ฐ ์บ๋ฆฌ์ด ์ค๋ ๋๊ฐ ๋ค๋ฅธ ์์ ์ํ์ ์ฌ์ฉํ ์ ์๋๋ก ํฉ๋๋ค. ์ด๋ฌํ ์์ ์ Mount/Unmount ์์ ์ด๋ผ ํฉ๋๋ค.
- Mount: ๊ฐ์ ์ค๋ ๋ ๋ด ์์ ์ด ์บ๋ฆฌ์ด ์ค๋ ๋์์ ์คํ ์ค์ธ ๊ฒฝ์ฐ
- Unmount: ๊ฐ์ ์ค๋ ๋ ๋ด ์์ ์ด I/O blocking ๋ฑ ์ํ๋ก ์บ๋ฆฌ์ด ์ค๋ ๋์์ ์ ์ ์ค๋จ๋ ๊ฒฝ์ฐ
์ด๋ฌํ ์์ ์ด ์ํ๋๋ ๋์ ๊ฐ์ ์ค๋ ๋๋ ์ํ๊ฐ ๋ณ๊ฒฝ๋๋๋ฐ, ๋ํ์ ์ผ๋ก ๊ฐ์ ์ค๋ ๋ ์์ ์ด I/O blocking ๋ฑ์ ์ํด ์ผ์ ์ ์ง๋ ๊ฒฝ์ฐ, PARKED ์ํ๋ก ์ ํ๋ฉ๋๋ค.
final class VirtualThread extends BaseVirtualThread { | |
private static final int NEW = 0; | |
private static final int STARTED = 1; | |
private static final int RUNNABLE = 2; | |
private static final int RUNNING = 3; | |
private static final int PARKING = 4; | |
private static final int PARKED = 5; | |
private static final int PINNED = 6; | |
private static final int YIELDING = 7; | |
private static final int TERMINATED = 99; | |
private static final int SUSPENDED = 256; | |
private static final int RUNNABLE_SUSPENDED = 258; | |
private static final int PARKED_SUSPENDED = 261; | |
} |
์ ์ฝ๋๋ VirtualThread ํด๋์ค์์ ๊ฐ์ ธ์จ ์ค์ ๊ฐ์ ์ค๋ ๋๊ฐ ์ฌ์ฉํ๋ ์ํ๊ฐ๋ค์ ๋๋ค. NEW ~ PARKED_SUSPEND๊น์ง ์ฌ๋ฌ ์ข ๋ฅ๊ฐ ์์ผ๋ฉฐ ๊ทธ ์ค ์์ฃผ ๋ณด์ด๋ ์ํ๊ฐ์ RUNNABLE, RUNNING, PARKED, PINNED๊ฐ ์์ต๋๋ค. ์ด๋ฅผ ํตํ์ฌ ๊ฐ์ ์ค๋ ๋๊ฐ ์์ฑ๋์ด ์์ ์ด ์ข ๋ฃ๋ ๋๊น์ง๋ฅผ ์ผ๋ จ์ ๊ณผ์ ์ผ๋ก ๋ณด์๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ํ๋์ ์์ ์ ๊ฐ์ ์ค๋ ๋๋ก ์์ฑ ํ ์บ๋ฆฌ์ด ์ค๋ ๋์ mount
- ํด๋น ์์ฒญ ์์ ์ I/O blocking์ด ๋ฐ์ํ๋ ๊ฒฝ์ฐ ์คํ ํ๋ ์์ ์์ฑํ๊ณ ์บ๋ฆฌ์ด ์ค๋ ๋์์ unmount
- ์ ์์ ์ด RUNNABLE ๋๋ ๋์ ๋ค๋ฅธ ์์ (๊ฐ์ ์ค๋ ๋) ์คํ
- RUNNABLE ์ํ๊ฐ ๋๋ฉด continue execution ๋ฉ์๋์ ๋๋จธ์ง ์์ ์คํ
- ์์ ์ข ๋ฃ
์ ๊ณผ์ ์ ๊ฐ์ ์ค๋ ๋๋ฅผ ์ด์ฉํ์ฌ ์ํํ ์์ ์ด ์ฑ๊ณต์ ์ผ๋ก ์ํํ์ฌ ์ข ๋ฃ๋ ๊ฒฝ์ฐ์ ๊ณผ์ ์ ๋๋ค. ํ์ง๋ง ์ค๋ ๋ ์์ ์ค ๋์ค์ ์คํจํ๋ ๊ฒฝ์ฐ์๋ ์ด๋ป๊ฒ ๋ ๊น์?
Pinning
๊ฐ์ ์ค๋ ๋๊ฐ ์บ๋ฆฌ์ด ์ค๋ ๋์์ unmount ๋์ง ๋ชปํ๊ณ ์๋ ์ํ๊ฐ์ด ์์ต๋๋ค. ์ด๋ฅผ PINNED ๋ก ์ ์ํ์๋๋ฐ, ๊ฐ๋ น ๊ฐ์ ์ค๋ ๋๊ฐ blocking ๋ฉ์๋ ํธ์ถ๋ก ์ธํด PARKING ์ํ๊ฐ ๋๋ฉด ์บ๋ฆฌ์ด ์ค๋ ๋๋ ํด๋น ์์ ์ ๋ํ ์คํ ํ๋ ์์ ํ ์์ญ์ ์ ์ฅํด๋๊ณ ๋ค๋ฅธ ์์ ์ ํ๋ ๊ฒ ์ ์์ ์ธ ๋ฐฉํฅ์ด์ง๋ง ์ด ์์ ์ด ์ ๋๋ก ์ด๋ค์ง์ง ์๋ ๊ฒฝ์ฐ PINNED ์ํ๊ฐ ๋๋ฉฐ ์ด๋ฅผ ํผ๋(PINNING)์ด๋ผ ํฉ๋๋ค.
๊ทธ๋ ๋ค๋ฉด ํผ๋์ ์ด๋ ํ ๊ฒฝ์ฐ์ ๋ฐ์ํ ๊น์?
- ์ฝ๋ ๋ด synchronized ๋ธ๋ก ์ฌ์ฉ ๋ฐ ๋ฉ์๋ ํธ์ถ
- native ๋ฉ์๋ ํน์ Java ์ธ๋ถ ํจ์(Java ์ธ ๋ค๋ฅธ ์ธ์ด๋ก ๊ตฌํ๋ ํจ์)๋ฅผ ํธ์ถ
๋ง์ฝ ํ๋ซํผ ์ค๋ ๋๋ฅผ ์ฌ์ฉํ์ฌ ๋์์ฑ ์ฒ๋ฆฌ๋ฅผ ๊ตฌํํ๋ค๋ฉด ๊ณ ๋ คํด์ผ ํ ๋ถ๋ถ ์ค์ ํ๋๋ ํ๋์ ์์์ ๋๊ณ ๋ ์ค๋ ๋๊ฐ ๋์์ ์ฌ์ฉํ๋ ๊ฒฝ์ ๋ฌธ์ ์ ๋๋ค. ์ด๋ฅผ ์ํด Java์์ synchronized ๋ธ๋ก์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค.
class Pair { | |
private int x, y; | |
public Pair(int x, int y) { | |
this.x = x; | |
this.y = y; | |
} | |
public void updateData(int x, int y) { | |
this.x = x; | |
this.y = y; | |
} | |
} | |
public class VirtualThreadPinningPoC { | |
private final Pair p = new Pair(2, 2); | |
// synchronized ๋ธ๋ก ์ฌ์ฉ | |
// ์ด ๋ธ๋ก์ ์ฌ์ฉํ๋ฉด ์์ฐจ์ ์ ๊ทผ์ ์ํํ๋ฏ๋ก ํผ๋ ๋ฐ์ | |
public synchronized void updateData(int x, int y) { | |
p.updateData(x, y); | |
} | |
} |
์ฝ๋๋ฅผ ์์๋ก ๋ค๋ฉด ์์ ๊ฐ์ ์ฝ๋๋ฅผ ๋งํ๋๋ฐ, ๋ ์ค๋ ๋๊ฐ ์๋ก ์ ๋ฐ์ดํธ๋ฅผ ํจ์ผ๋ก์จ ์ ๋ฐ์ดํธ ์๋ฒ์ด ๋ค์ํค๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๊ธฐ ๋๋ฌธ์ synchronized ๋ฉ์๋๋ก ์ ๋ฐ์ดํธํ๋๋ก ๊ตฌํํ ์์์ ๋๋ค.
ํ์ง๋ง ๊ฐ์ ์ค๋ ๋์์๋ ์ด๋ฌํ ๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด ๊ฐ์ ์ค๋ ๋์์ blocking์ด ๋ฐ์ํ๋ ๊ฒ์ด ์๋ ์บ๋ฆฌ์ด ์ค๋ ๋์์ blocking์ด ๋ฐ์ํ๊ธฐ ๋๋ฌธ์ ์์นซํ๋ฉด ๊ฐ์ ์ค๋ ๋ ์ค์ผ์ค๋ฌ์์ ํด๋น ์บ๋ฆฌ์ด ์ค๋ ๋๋ฅผ ๊ด๋ฆฌํ์ง ๋ชปํ๊ฒ ๋๋ ํ์์ด ๋ฐ์ํฉ๋๋ค. ์ด๋ ๊ณง ์ฑ๋ฅ ์ ํ๋ก ์ด์ด์ง์ฃ .
class Pair { | |
private int x, y; | |
public Pair(int x, int y) { | |
this.x = x; | |
this.y = y; | |
} | |
public void updateData(int x, int y) { | |
this.x = x; | |
this.y = y; | |
} | |
} | |
public class VirtualThreadPinningPoC { | |
private final Pair p = new Pair(2, 2); | |
private static final ReentrantLock LOCK = new ReentrantLock(); | |
// ReentrantLock ์ฌ์ฉ | |
// Lock์ ์ฌ์ฉํ๋ฉด ๊ฐ์ ์ค๋ ๋๊ฐ PARKED ์ํ์ผ๋ก ์ ํ๋จ | |
public void updateData(int x, int y) { | |
LOCK.lock(); | |
try { | |
p.updateData(x, y); | |
} finally { | |
LOCK.unlock(); | |
} | |
} | |
} |
์ด๋ด ๋๋ ๋๊ธฐ ๋ธ๋ก/๋ฉ์๋๋ฅผ ํธ์ถํ๊ธฐ๋ณด๋ค๋ ReentrantLock๊ณผ ๊ฐ์ ์ธ๋งํฌ์ด๋ฅผ ์ด์ฉํ์ฌ ์บ๋ฆฌ์ด ์ค๋ ๋๋ฅผ blockingํ์ง ์๊ณ ๊ฐ์ ์ค๋ ๋๋ฅผ PARKED๋ก ์ ๋ํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค. ์ด ๋ฌธ์ ๋ ๋ฒ๊ทธ์ ํด๋นํ๋ฉฐ Java 21์๋ ๊ณ์ ์กด์ฌํ์ง๋ง ์ฐจ๊ธฐ ๋ฒ์ ์์ ์ด๋ฅผ ์์ ํ๋ค๊ณ ํฉ๋๋ค.
๋ํ Java์์ C/C++ ๊ณ์ด์ ์ฝ๋๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด JNI(Java Native Interface)๋ฅผ ์ด์ฉํ๋๋ฐ, ์ด๋ฅผ ํตํด ํจ์๋ฅผ ํธ์ถํ ๋๋ ํผ๋์ด ๋ฐ์ํฉ๋๋ค. ๋ค๋ง JNI์์๋ isVirtualThread ๋ฉ์๋๋ฅผ ์ง์ํ์ฌ ํธ์ถํ๋ ค๋ ์ค๋ ๋๊ฐ ํ๋ซํผ ์ค๋ ๋์ธ์ง, ๊ฐ์ ์ค๋ ๋์ธ์ง์ ๋ํ ๊ฐ์ ๋ด๋ ค์ฃผ๋ฏ๋ก ์ด๋ฅผ ํตํด์ ๊ฐ์ ํ ์๋ ์์ด ๋ณด์ ๋๋ค.
Thread Pooling
ํ๋ซํผ ์ค๋ ๋๋ฅผ ์ฌ์ฉํ ๋๋ ๊ทธ ์์ฑ ๋น์ฉ์ด ํฌ๊ธฐ ๋๋ฌธ์ ์ค๋ ๋๋ฅผ ๋ฏธ๋ฆฌ ์์ฑํ์ฌ ์ฌ์ฉํ ๋๋ง๋ค ๋นผ์ ์ฐ๊ธฐ ์ํด ํ๋ง(Pooling)์ ์ฌ์ฉํ์์ต๋๋ค. ๊ทธ๋ ๋ค๋ฉด ๊ฐ์ ์ค๋ ๋๋ ํ๋งํ ์ ์์๊น์?
Java ์์ง๋์ด๋ค์ ๊ฐ์ ์ค๋ ๋๋ฅผ ํ๋งํ์ง๋ง๋ผ(Don't pool virtual threads!)๊ณ ํฉ๋๋ค. ํ๋ง์ ์ด์์ฒด์ ์ค๋ ๋์ ๊ฐ์ ๊ณ ๊ฐ์ ๋น์ฉ์ด ๋ค์ด๊ฐ๋ ๊ณณ์์ ์ฌ์ฉํ๊ธฐ ์ํด ๊ณ ์๋ ๊ฒ์ธ๋ฐ, ๊ฐ์ ์ค๋ ๋๋ ๊ทธ ๋ฆฌ์์ค์ 1/10 ์ ๋ ๋ฐ์ ๋์ง ์์ผ๋ฏ๋ก ํ๋งํ์ง ์์๋ ๋๋ค๋ ๊ฒ์ ๋๋ค.
ํ์ง๋ง ํน์ ๋ฉ์๋์ ์ฌ์ฉํ๋ ๊ฐ์ ์ค๋ ๋ ๊ฐฏ์๋ฅผ ์ ํํ๋ ๊ฑธ ๋ชฉ์ ์ผ๋ก ํ๊ณ ์๋ค๋ฉด ์ค๋ ๋ ํ ๋์ ์ธ๋งํฌ์ด ๊ธฐ๋ฒ์ผ๋ก ์ ๊ทผํด ๋ณผ ์ ์์ต๋๋ค.
// WITH THREAD POOL | |
private static final ExecutorService HTTP_CONNECTION_POOL = Executors.newFixedThreadPool(32); | |
public <T> Future<T> httpRequest(Callback<T> httpTask) { | |
// pool limit to 32 concurrent queries | |
return HTTP_CONNECTION_POOL.submit(httpTask); | |
} |
ํ๋ซํผ ์ค๋ ๋์์๋ ์์ ๊ฐ์ด ExecutorService์ ๊ตฌํ์ฒด์ธ newFixedThreadPool ๋งค๊ฐ๋ณ์๋ก ์ค๋ ๋ ๊ฐฏ์๋ฅผ ๋ฐ์ ์ค๋ ๋๋ฅผ ๋ฏธ๋ฆฌ ์์ฑํ์ฌ ์์ํ ์ ์์์ต๋๋ค.
// WITH SEMAPHORE | |
private static final Semaphore HTTP_SEMAPHORE = new Semaphore(32); | |
public <T> T httpRequest(Callable<T> httpTask) throws Exeception { | |
// semaphore limits to 32 concurrent queries | |
HTTP_SEMAPHORE.acquire(); | |
try { | |
return httpTask.call(); | |
} finally { | |
HTTP_SEMAPHORE.release(); | |
} | |
} |
ํผ๋ ์ด์์ ๋ง์ฐฌ๊ฐ์ง๋ก ์ธ๋งํฌ์ด ๊ธฐ๋ฒ์ ์ด์ฉํ์ฌ ํน์ ๋ฉ์๋์์ ๊ฐ์ ์ค๋ ๋ ์์ฑ ์๋ฅผ ์ ํํด ๋ณผ ์ ์์ต๋๋ค. ์ ์ฝ๋๋ ์ ํ๋ ์ธ๋งํฌ์ด๋ฅผ ์์ฑํด๋๊ณ , ํธ์ถ๋ง๋ค ์นด์ดํ ์ ํ๋ฉฐ ํธ์ถ์ด ์๋ฃ๋๋ฉด ๋ค์ ๊ทธ ์นด์ดํ ์ ๋ณต๊ตฌํ๋ ๋ฐฉ์์ ๋๋ค.
Scoped Value
๊ฐ์ ์ค๋ ๋์์๋ ํ๋ซํผ ์ค๋ ๋์ ๋ง์ฐฌ๊ฐ์ง๋ก ThreadLocal ๋ณ์๊ฐ ์กด์ฌํฉ๋๋ค. ์๋ฐ์์ ์ ๊ณตํ๋ ์ค๋ ๋ ๋ก์ปฌ ๋ณ์์๋ ThreadLocal๊ณผ InheritableThreadLocal์ ์ฌ์ฉํ ์ ์๋๋ฐ, ๊ฐ์ ์ค๋ ๋๋ ๊ณต์์ ์ธ ์ค๋ ๋ ํ๋ง์ ์ง์ํ์ง ์๊ธฐ ๋๋ฌธ์ ํ๋์จ์ด ๋ฆฌ์์ค๋งํผ ์ต๋ํ์ ๊ฐ์ ์ค๋ ๋๋ฅผ ์ฌ์ฉํ๊ฒ ๋ฉ๋๋ค. (์ํ๋ฉด 1,000,000๊ฐ ์ด์๊น์ง๋...)
ํ์ง๋ง ์ด๋ด ๊ฒฝ์ฐ ์๋ง์ ๊ฐ์ ์ค๋ ๋๋ค์ด ๊ณต์ ๋ ๋ฆฌ์์ค์ ์ ๊ทผํ๊ธฐ ์ํด ๊ณ์ ๋๊ธฐํ๊ฒ ๋๋๋ฐ, ๋ง์ฝ InheritableThreadLocal๊ณผ ๊ฐ์ด ๋น์ฉ์ด ๋น์ผ ๋ฆฌ์์ค์ ํ๋ง์ ์ํํ๊ฒ ๋๋ฉด ๊ฐ์ ์ค๋ ๋๊ฐ ์ฐจ์งํ๋ ๋ฉ๋ชจ๋ฆฌ์ ํฌ๊ธฐ๊ฐ ๋ ์ปค์ง๊ฒ ๋ฉ๋๋ค.
์ด๋ฌํ ๋ฌธ์ ๋๋ฌธ์ Java 20์์๋ JEP 429: Scoped Values (Incubator) ๊ฐ ๊ณ ์๋์์ผ๋ฉฐ ๊ฐ์ ์ค๋ ๋ ๋ด ํน์ ๊ฐ์ ์ค๋ ๋ ๊ฐ์ ๊ณต์ ํ ์ ์๋ ๋ถ๋ณ ๋ฐ์ดํฐ ํด๋์ค๋ฅผ ํตํด ThreadLocal์ด ๊ฐ์ง ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ณ ์ ํ์ต๋๋ค. ํ์ง๋ง ThreadLocal์ 100% ๋์ฒดํ๊ธฐ ์ํ ๊ฒ์ ์๋๋๋ค.
// ThreadLocal Sample | |
private static final ThreadLocal<Principal> PRINCIPAL_TL = new ThreadLocal(); | |
// ScopedValue example | |
private static final ScopedValue<Principal> PRINCIPAL_SV = ScopedValue.newInstance(); |
์ฝ๋ ์์ฒด์๋ ํฐ ์ฐจ์ด๊ฐ ์์ง๋ง ThreadLocal์ ๊ฐ๋ณ์ฑ, ScopedValue๋ ๋ถ๋ณ์ฑ์ด๋ผ๋ ์ ์ ์ฐจ์ด๊ฐ ์์ต๋๋ค. ๋ํ ThreadLocal์ ์ปจํ ์ด๋ ์๋ช ๊ณผ ๋์ผํ๊ฒ ๊ฐ์ ธ๊ฐ์ remove ๋ฉ์๋๋ฅผ ๋ช ์์ ์ผ๋ก ํธ์ถํ๊ฑฐ๋, ์๋ก์ด ๊ฐ์ ์ค์ ํ๋ ๋ฐฉ๋ฒ์ผ๋ก ๊ทธ ์๋ช ์ ๊ฐฑ์ ํ์ง๋ง ScopedValue๋ ํ์ฌ ์ฌ์ฉ ์ค์ธ ์ค๋ ๋ ์๋ช ์ ๋ฐ๋ผ๊ฐ๊ณ , ๋ฉ์๋๋ก ๊ฐ์ ํ ๋นํ์ง ์๊ณ ์ค์ฝํ๋ก ํ ๋นํ๋ค๋ ์ ์์ Python์ yield์ ์ฝ๊ฐ ์ ์ฌํฉ๋๋ค.
๋ง์น๋ฉฐ..
์ด๋ฒ ํฌ์คํธ์์๋ ๊ฐ์ ์ค๋ ๋๋ฅผ ๊ฐ๋จํ๊ฒ ๋ณด๋ค๋ ์ข ๋ ๊น๊ฒ ํค์ง์ด๋ณด๋ฉด์ ๊ฐ์ ์ค๋ ๋๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด ๋ฐ๋์ ์์์ผ ํ ๋ถ๋ถ์ ๋ํด ์ค์ ์ ์ผ๋ก ๋ค๋ค๋ดค์ต๋๋ค.
์๋ฐ์์ ๋น๋๊ธฐ ํ๋ก๊ทธ๋๋ฐ์ ๊ฐ์ ์ค๋ ๋๊ฐ ๋์ค๊ธฐ ์ด์ ์๋ ๊ต์ฅํ ์ด๋ ค์ด ํจ๋ฌ๋ค์์ผ๋ก ์๋ ค์ ธ ์์ต๋๋ค. ์ฌ์ฉํ๊ธฐ ์ด๋ ค์ด Callable ๋ฐฉ์, ๊ฐ๋ฐ์๊ฐ ๋น๋๊ธฐ ํ๋ก๊ทธ๋๋ฐ์ ๋ํ ๋ง์ ์ง์ ์๊ตฌ, ํจ์ ์ ๋ฌธ์ ๋ฑ ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ฅผ ์ํด ๋ง์ ๊ณ ๋ฏผ์ ํด์ผ ํ๊ณ , ๊ทธ๋งํผ ์ฅ๋ฒฝ์ด ๋์์ง๋ง ๊ฐ์ ์ค๋ ๋์ ๋ฑ์ฅ์ผ๋ก ํจ์ ์ ๋ฌธ์ ์ ๊ตฌ์กฐ์ ๋์์ฑ์ ๋ถํ์ํด์ผ๋ก์จ ๊ฐ๋ฐ์๊ฐ ์ข ๋ ์ฌ์ฉํ๊ธฐ ์ฝ๊ฒ ํด์ฃผ์์ฃ .
์์ง ๊ตญ๋ด์์ Java 21 ๋์ ์ด ๋ง์ด ๋ ๊ณณ์ ์์ง๋ง ๊ฐ์ ์ค๋ ๋๋ฅผ ํตํด ์์ง๊น์ง Java ํ์๋ฆฌ ์ ๋ฒ์ ์ ๋จธ๋ฌผ๋ฌ ๊ณ์๋ ๋ถ๋ค๋ ์ด๋ฒ ๊ธฐํ์ ํฐ ๋ณํ๋ฅผ ๊ฐ์ ธ๋ณด๋ ๊ฒ์ ์ด๋จ๊น์?
์ฐธ๊ณ :
Oracle Tech Blog(https://blogs.oracle.com/javamagazine/post/java-loom-virtual-threads-platform-threads)