Java dünyasında yıllarca thread sayısı pahalı bir kaynak olarak düşünüldü.
Bu nedenle thread pool boyutları, queue capacity ve executor tuning backend geliştirmede önemli optimizasyon alanlarından biri haline geldi.
Project Loom ile gelen Virtual Threads bu varsayımı ciddi biçimde değiştirdi.
Virtual Threads, Java 21 ile JEP 444 kapsamında kalıcı özellik haline geldi. OpenJDK’nin amacı özellikle thread-per-request modelini korurken I/O yoğun uygulamalarda yüksek concurrency sağlayabilmek.
Ancak burada kritik bir yanlış anlaşılma var.
Virtual Thread uygulamanızı otomatik olarak daha hızlı yapmaz.
Throughput ile execution speed aynı şey değildir
Bir HTTP isteğinin 200 ms süren database sorgusu yaptığını düşünelim.
Platform thread modelinde bu 200 ms boyunca bir OS thread bloklanabilir. Aynı anda binlerce request geldiğinde thread sayısı problem olmaya başlar.
Virtual Thread ise blocking I/O sırasında carrier thread’i bırakabilir ve JVM aynı underlying thread üzerinde başka bir işi çalıştırabilir.
Sonuç tek bir request’in daha hızlı bitmesi değildir.
Daha fazla request’in aynı anda bekleyebilmesidir.
Bu nedenle Virtual Threads özellikle I/O-bound sistemlerde anlamlıdır.
CPU-bound workload’da ise fizik değişmez. Sekiz core üzerinde aynı anda on bin CPU-intensive hesaplama çalıştırmanın yolu Virtual Threads değildir.
Spring Boot tarafı
Güncel Spring Boot dokümantasyonunda Virtual Threads şu property ile açılabiliyor:
spring.threads.virtual.enabled=true
Spring dokümantasyonu Java 21 veya üzerini gerektiriyor ve güncel sürümlerde Java 24+ kullanımını en iyi deneyim için öneriyor. Aynı dokümantasyon pinned virtual thread durumlarının throughput’u düşürebileceği konusunda da uyarıyor.
Bunun yanında önemli bir ayrıntı daha var.
Virtual Threads kullanmaya başladığınızda bazı klasik thread-pool tuning ayarları anlamını kaybeder. Spring’in güncel dokümantasyonu virtual threads aktifken ilgili executor’ın thread pool yerine virtual-thread tabanlı çalıştığını belirtiyor.
Database connection pool hâlâ var
Virtual Threads’in sık yapılan yanlış kullanım örneği burada ortaya çıkıyor.
Artık 20.000 concurrent request kabul edebiliyor olmanız PostgreSQL’in 20.000 eş zamanlı connection kaldıracağı anlamına gelmez.
Örneğin HikariCP:
20.000 Virtual Thread
↓
50 Database Connection
↓
PostgreSQL
şeklinde çalışmaya devam eder.
Yani bottleneck ortadan kalkmaz. Sadece başka bir noktaya taşınabilir.
Bu kötü bir şey değildir. Connection pool burada doğal bir backpressure mekanizması görevi görür.
Eski alışkanlıkları taşımayın
OpenJDK dokümantasyonundaki önemli önerilerden biri Virtual Threads’in pool edilmemesidir. Çünkü bunlar zaten ucuz ve kısa ömürlü olacak şekilde tasarlanmıştır.
Platform thread dünyasındaki:
200 request
→ 20 thread pool
→ queue
modelini düşünmeden:
task
→ one virtual thread
yaklaşımına geçebilirsiniz.
Virtual Threads Java concurrency’sini ortadan kaldırmadı.
Ama uzun yıllardır concurrency yüzünden uyguladığımız bazı savunma mekanizmalarını artık yeniden değerlendirmemiz gerekiyor.
En doğru kullanım biçimi de benchmark’tır.
Property’yi açmak değil.