Virtual Threads vs Platform Threads
30 saniyede özet
Virtual thread daha hızlı değil, beklerken daha ucuz. Veritabanı ya da ağ cevabı beklerken altındaki gerçek thread'i bırakır; kazanç hafiflikten değil, buradan gelir.
Beklemek bedava görünür, ama kimin beklediği önemlidir. Masada dikilen garson başka müşteriye bakamaz.
Adım adım oku
- Klasik modelde garson 1 numaralı masanın yemeği pişene kadar orada bekler.
- 2 ve 3 numaralı masalar, garson boşalana kadar sırada kalır.
- Virtual thread modelinde garson siparişi mutfağa bırakır ve hemen sonraki masaya geçer.
- Tek garson üç masanın siparişini de mutfağa ulaştırdı. Bekleme artık garsonu tutmuyor.
-
Bayt: Sunucum yoğun saatte tıkandı. İşlemci boş, bellek dolu. Neyi bekliyor bu?
-
Sen: Thread'lerin hepsi veritabanından cevap bekliyor olabilir mi?
-
Bayt: Tam olarak. Her biri masanın başında dikilip yemeği bekleyen bir garson gibi.
-
Bayt: Virtual thread'de garson siparişi mutfağa bırakıp sonraki masaya geçer. Aynı ekip, çok daha fazla masa!
Bir web uygulamasında her gelen istek bir thread alır ve iş bitene kadar bırakmaz. Havuzda 200 thread varsa aynı anda en fazla 200 istek işleyebilirsin.
Sorun şu: bu isteklerin çoğu zamanının büyük kısmını bir veritabanı veya HTTP cevabını bekleyerek geçirir. Thread’lerin çoğu hiçbir iş yapmadan yer tutar.
Havuzu büyütmek neden çözüm değil
Havuzu 200'den 2000 thread'e çıkarsan sorun çözülür mü? Cevabı göster
Bir süre idare eder, sonra iki duvara çarparsın: bellek ve bağlam değiştirme.
- Bellek. Her platform threadKlasik Java thread'i: bir OS thread'ine birebir bağlıdır ve bloklandığında onu tutar. Yığını sabit boyutludur, bu yüzden sayısı sınırlıdır.Sözlükte gör → bir OS thread’idir ve kendine ~1 MB stack ayırır. 2000 thread ≈ 2 GB, hiçbir iş yapmadan.
- Geçiş maliyeti. İşletim sistemi binlerce thread arasında sürekli geçiş yapar; bu geçişlerin kendisi işlemci zamanı yer.
Kafam karıştı, daha basit anlat
Her thread kendine büyük bir masa ayırıyor, çoğu zaman da masada oturup cevap bekliyor. Binlerce masa kurmaya kalkarsan salon yetmez.
Virtual thread'leri havuzda tutmak (`newFixedThreadPool` gibi) iyi bir fikir midir?
Virtual thread ne yapıyor
Virtual thread, işletim sisteminin gerçek bir thread’i değildir. Onu JVM yönetir ve az sayıda gerçek thread’in, yani carrier threadBir virtual thread'i o an çalıştıran gerçek OS thread'i. Virtual thread blokladığında taşıyıcı serbest kalır ve başka bir virtual thread'e geçer.Sözlükte gör → thread’lerin (varsayılan olarak çekirdek sayısı kadar) sırtında taşınır.
Kritik davranış:
Virtual thread bir cevap beklemeye başladığında altındaki thread’den iner (unmount): kaldığı yer heap’e not edilir, altındaki thread başka bir işe geçer. Cevap gelince herhangi bir boş thread’e geri biner (mount) ve kaldığı yerden devam eder.
Yani beklemek artık neredeyse bedava. Kod yukarıdan aşağı düz okunur, ama altta kimse boşuna beklemez.
// Platform thread: havuz 200 ise eşzamanlılık tavanı 200var executor = Executors.newFixedThreadPool(200);
// Virtual thread: her görev için bir thread, tavan iş yükütry (var executor = Executors.newVirtualThreadPerTaskExecutor()) { for (var request : requests) { executor.submit(() -> { var user = userClient.fetch(request.userId()); // bloklar ama carrier'ı bırakır return render(user); }); }}Spring Boot 3.2+ için tek satır yeter:
spring.threads.virtual.enabled=trueKafam karıştı, daha basit anlat
Virtual thread, beklerken masadan kalkan bir çalışan gibidir. Cevap gelince boş bir masaya oturup devam eder. Böylece az masayla çok iş döner.
Virtual thread ile platform thread arasındaki temel fark nedir?
Kendin ölç
Aşağıdaki simülatörde her kare bir görev. Önce varsayılan ayarla (platform, 8 thread) çalıştır, sonra modeli virtual yap ve aynı iş yükünü izle.
Platform thread vs virtual thread
Tohum 11Havuzdaki OS thread’leri (0/8)
Görevler · aynı anda uçuşta: 0 · en yüksek: 0
- sırada: 60
- CPU: 0
- I/O bekliyor: 0
- bitti: 0
- Tamamlanan
- 0/60
- En yüksek eşzamanlılık
- 0
- Yaklaşık stack belleği
- 0 KB
Şu an ne oldu?
Havuz dolu — 60 görev sırada
8 thread'in hepsi meşgul. I/O bekleyen bir thread hiçbir iş yapmıyor ama yerini de kimseye vermiyor.
Aklında kalsın: Thread-per-request modelinde eşzamanlılık tavanı havuz boyutudur. Havuzu büyütmek çözüm değil: her thread ~1 MB stack ayırır ve bağlam değiştirme maliyeti artar.
Görevler0/2
Aynı carrier sayısıyla havuzun 4 katından fazla görevi aynı anda uçuraçık
İpucu
Modu virtual thread yap. I/O’da bekleyen bir virtual thread carrier’ı bırakır, o yüzden eşzamanlılık havuz boyutuna bağlı kalmaz.
Bir virtual thread’i carrier’a çivile (pinning)açık
İpucu
Virtual thread modunda "I/O synchronized içinde" seçeneğini aç. Monitor tutan bir thread I/O’da carrier’ı bırakamaz.
Olay günlüğü (0)
Henüz olay yok. Oynat veya adımla.
Sırayla dene:
- Platform, havuz 8, I/O 12. Aynı anda en fazla 8 görev ilerliyor; geri kalanı sırada bekliyor. “En yüksek eşzamanlılık” değeri havuz boyutunda takılı kalıyor.
- Modeli
virtualyap. Carrier sayısı hâlâ 8, ama uçuştaki görev sayısı çok daha yüksek. Sınır artık havuz değil, iş yükü. - I/O süresini 1 yap. İki model neredeyse aynı. Bekleme yoksa kazanacak bir şey de yok.
synchronizedanahtarını aç. Virtual thread’in avantajı anında kayboluyor.
Simülatörün altındaki Görevler listesi bu iki gözlemi hedefe çeviriyor.
Her iş yükünü, virtual thread'lerin kazandırıp kazandırmadığına göre ayır.
Pinning: nerede takılır?
Virtual thread bazı durumlarda carrier’dan inemez. Buna pinningVirtual thread'in taşıyıcısını bırakamadan bloklanması. Olduğunda virtual thread'in tüm kazancı kaybolur — taşıyıcı OS thread'i orada beklemeye başlar.Sözlükte gör → denir ve en sık sebebi synchronized bloğudur:
public class RateLimiter { // synchronized içinde bloklayıcı çağrı: virtual thread pinlenir public synchronized Response call(Request request) { return httpClient.send(request); // carrier boyunca tutulur }
private final ReentrantLock lock = new ReentrantLock();
public Response call(Request request) { lock.lock(); try { return httpClient.send(request); // burada unmount edebilir } finally { lock.unlock(); } }}ReentrantLock bu sorunu yaşamaz: kilidi beklerken de, kilidi tutup bir cevap beklerken de virtual thread aşağı inebilir. Kural basit: virtual thread’e geçerken, içinde bekleme olan synchronized bloklarını ReentrantLock’a çevir.
Diğer pinning sebepleri· istersen atla
Native metot (JNI) çağrısı içindeyken de pinning olur. Java 21’de -Djdk.tracePinnedThreads=full bunları yakalar.
JDK 24 (JEP 491) ile synchronized artık pinlemiyor ve bu bayrak kaldırıldı; pinning JFR’ın jdk.VirtualThreadPinned olayıyla izlenir. Java 21’deysen kural geçerli.
Neyi çözmez? Virtual thread sihirli bir hızlandırıcı değil; tablo nerede işe yaramadığını gösteriyor.
| İş yükü | Virtual thread kazandırır mı |
|---|---|
| Çok sayıda eşzamanlı HTTP/DB çağrısı | Evet — asıl kullanım alanı |
| Ağır CPU hesabı (görüntü işleme, şifreleme) | Hayır — paralellik hâlâ çekirdek sayısıyla sınırlı |
| Az sayıda uzun süren görev | Hayır — zaten havuz doymuyor |
| ThreadLocal’a çok veri koyan kod | Hayır, zararlı — milyonlarca thread × ThreadLocal = bellek patlaması |
Eşzamanlılık (concurrency) ile paralellik (parallelism) tam burada ayrışır. Virtual thread eşzamanlılığı ucuzlatır; paralellik hâlâ CPU çekirdeği sayısı kadardır.
Kafam karıştı, daha basit anlat
synchronized içinde beklerken virtual thread masadan kalkamayabilir, masayı boşuna tutar. Bekleme olan yerlerde ReentrantLock kullan: onunla kalkabilir.
Bir virtual thread'in `synchronized` blok içinde bloklayıcı I/O yapması neden sorundur?
Virtual thread kullanan bir kodda `ThreadLocal` neden dikkat ister?
Yanında gelen: Structured Concurrency
Java 21’in önizleme özelliği, birden fazla virtual thread’i tek bir kapsamda yönetir:
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { var user = scope.fork(() -> userClient.fetch(id)); var orders = scope.fork(() -> orderClient.fetch(id));
scope.join().throwIfFailed(); // biri patlarsa diğeri otomatik iptal return new Profile(user.get(), orders.get());}Kazanç: alt görevler kapsam dışına sızamaz, biri başarısız olunca diğerleri iptal edilir ve stack trace anlamlı kalır.
Aşağıdaki örnek bir bankanın yurt dışı ödemelerinden: her ödeme gönderilmeden önce yaptırım listelerine karşı taranır. İş neredeyse tamamen beklemedir; sınırı veritabanı havuzu koyar.
Derinleş · Virtual thread'lerle yaptırım taraması: uçtan uca 5 dosya · ~104 satır · ilk okumada atlayabilirsin
Kendini sına
Önce hızlı bir ısınma: puan yok, kayıt yok.
Virtual thread'ler hesap ağırlıklı işleri hızlandırır.
Virtual thread'ler hangi iş yükünde anlamlı kazanç sağlar?
Aklında kalacak üç şey
- 1 Virtual thread bir cevap beklerken altındaki gerçek thread'den iner, o thread de başka bir işe geçer. Klasik thread bunu yapamaz.
- 2 Sadece hesap yapan işte kazanç yok. Virtual thread aynı anda bekleyebilen iş sayısını artırır, işlemci çekirdeği sayısını değil.
- 3 Java 21'de synchronized blok içindeki virtual thread aşağı inemez; buna pinning denir. Bu yüzden synchronized yerine ReentrantLock önerilir.
5 kart sonraki derste seni bekliyor
Bu dersin üstüne kurulanlar
Bunlar bu dersi temel alıyor; hazır olduğunda devam edebilirsin.
- Core Java & EşzamanlılıkThreadLocal ve ScopedValue — Havuzdaki Thread Neyi Hatırlıyor?Havuzdaki bir thread, bir önceki kullanıcının bilgisini nasıl hatırlayabilir?Derse git
- Core Java & EşzamanlılıkCompletableFuture ve Structured ConcurrencyÜç servisi aynı anda çağırıp yalnızca en yavaşı kadar beklemek mümkün mü?Derse git
- Spring Boot EkosistemiWebFlux ve Event Loop — Bloklayan Tek Satır Neyi Durdurur?Birkaç işçi binlerce isteği nasıl taşır? Ta ki biri bir yerde beklemeye başlayana kadar.Derse git