# Spring Boot 3.4 Virtual Threads: Mülakat Soruları ve Performans Karşılaştırmaları > Java 21 Virtual Threads'i Spring Boot 3.4 ile öğrenin: 15 mülakat sorusu, performans karşılaştırmaları ve teknik mülakatlarda fark yaratacak göç desenleri. - Published: 2026-03-15 - Updated: 2026-05-01 - Author: SharpSkill - Tags: spring boot, virtual threads, java 21, project loom, performance - Reading time: 16 min --- Virtual Threads, Java 21'in en önemli yeniliklerinden biridir ve Spring Boot 3.4 onları yerel olarak destekler. Project Loom'un bu özelliği, backend uygulamalarında eşzamanlılık yönetimini köklü biçimde değiştirir. Teknik mülakatlar artık iç mekanizmaların, uygun kullanım senaryolarının ve sık rastlanan tuzakların kavranmasını ölçer. > **Hazırlık İpucu** > > Mülakatçılar Virtual Threads'i gerçekten anlayan adayları, onları körlemesine kullananlardan ayırır. Ne zaman KULLANILMAMASI gerektiğini bilmek, faydalarını bilmek kadar önemlidir. ## Virtual Threads Temelleri ### Soru 1: Virtual Thread nedir ve Platform Thread'den nasıl farklıdır? Virtual Thread, işletim sistemi yerine JVM tarafından yönetilen hafif bir thread'dir. Platform Threads'in (klasik thread'ler) aksine Virtual Threads doğrudan OS thread'lerine eşlenmez. JVM, bunlardan milyonlarcasını çok düşük bellek kullanımıyla oluşturabilir. ```java // VirtualThreadDemo.java // Comparing thread creation approaches public class VirtualThreadDemo { public void demonstrateDifference() { // Platform Thread: ~1MB stack per thread // Practical limit: a few thousand on a standard JVM Thread platformThread = new Thread(() -> { performBlockingOperation(); }); // Virtual Thread: ~a few KB per thread // Can create millions without issues Thread virtualThread = Thread.ofVirtual().start(() -> { performBlockingOperation(); }); } // A Virtual Thread "mounts" onto a Platform Thread (carrier) // During I/O blocking, it releases the carrier for other Virtual Threads private void performBlockingOperation() { try { Thread.sleep(1000); // Virtual Thread detaches from carrier here } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } ``` Temel fark, bloklanma sırasındaki davranıştadır. Platform Thread bloklandığında (I/O, sleep) OS thread'ine bağlı kalır. Virtual Thread ise taşıyıcı thread'den "unmount" olur ve taşıyıcının başka Virtual Threads tarafından kullanılmasına izin verir. ### Soru 2: Spring Boot 3.4'te Virtual Threads nasıl etkinleştirilir? Spring Boot 3.4, Virtual Threads etkinleştirmesini tek bir konfigürasyon özelliğine indirgemiştir. Tüm framework otomatik olarak adapte olur: Tomcat, REST controller'ları ve bloklayıcı çağrılar bu optimizasyondan anında yararlanır. ```yaml # application.yml # Global Virtual Threads activation spring: threads: virtual: enabled: true # Optional Tomcat pool configuration server: tomcat: threads: max: 200 # Less critical with Virtual Threads min-spare: 10 ``` ```java // WebConfig.java // Programmatic activation if needed @Configuration public class WebConfig { @Bean public TomcatProtocolHandlerCustomizer virtualThreadCustomizer() { // Each HTTP request runs on a Virtual Thread return protocolHandler -> { protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor()); }; } } ``` Etkinleştirme Tomcat'in davranışını değiştirir: sabit bir thread havuzu yerine her istek kendi Virtual Thread'ini alır. Bu yaklaşım, klasik thread havuzu darboğazını ortadan kaldırır. ### Soru 3: "Mounting" ve "unmounting" kavramlarını açıklayın Mounting, bir Virtual Thread'in taşıyıcı thread'e (Platform Thread) bağlanmasını ifade eder. Unmounting ise bloklayıcı işlemler sırasında gerçekleşir ve taşıyıcıyı diğer Virtual Threads için serbest bırakır. Bu mekanizma CPU kaynaklarının optimum kullanımını sağlar. ```java // MountingDemo.java // Illustrating the mounting/unmounting cycle public class MountingDemo { public void demonstrateMounting() { Thread.ofVirtual().start(() -> { // MOUNTED: Virtual Thread is using a carrier thread System.out.println("Carrier: " + getCurrentCarrier()); // UNMOUNTING: releases carrier during blocking performDatabaseQuery(); // Blocking JDBC call // REMOUNTED: may be on a different carrier System.out.println("New carrier: " + getCurrentCarrier()); }); } // Blocking operations trigger unmounting automatically private void performDatabaseQuery() { // JDBC connection, file read, network call... // Virtual Thread detaches during I/O wait } private String getCurrentCarrier() { // Gets the current carrier thread name return Thread.currentThread().toString(); } } ``` Bu mekanizma geliştirici için saydamdır. Kod klasik imperatif tarzda yazılır, ancak JVM taşıyıcı kullanımını otomatik olarak optimize eder. Birkaç taşıyıcıdan oluşan bir havuz milyonlarca Virtual Thread'e hizmet verebilir. > **Teknik Not** > > Taşıyıcı thread sayısı varsayılan olarak CPU çekirdek sayısına eşittir. JVM bu havuzu ForkJoinPool aracılığıyla dinamik biçimde ayarlar. ## Kullanım Senaryoları ve Anti-Patternler ### Soru 4: Virtual Threads ne zaman performans kazancı sağlar? Virtual Threads, I/O ağırlıklı yüklerde parlar: harici REST çağrıları, veritabanı sorguları, dosya okumaları. Bu işlemler zamanın büyük kısmını beklemekle geçirir; bu sırada Virtual Thread taşıyıcısını serbest bırakır. ```java // IOBoundService.java // Ideal case for Virtual Threads @Service public class IOBoundService { private final RestClient restClient; private final UserRepository userRepository; // Each call involves network and database waiting // Virtual Threads shine here public UserProfile enrichUserProfile(Long userId) { // DB call - VT detaches during SQL query User user = userRepository.findById(userId).orElseThrow(); // External REST call - VT detaches during HTTP wait ExternalData externalData = restClient .get() .uri("/api/external/{id}", userId) .retrieve() .body(ExternalData.class); // Data aggregation return new UserProfile(user, externalData); } } ``` Kazanç çok daha fazla eşzamanlı isteğin işlenmesinden gelir. 200 Platform Thread ve 100ms süren isteklerle maksimum throughput 2.000 req/s'dir. Virtual Threads ile aynı makinede 50.000+ req/s mümkündür. ### Soru 5: Virtual Threads ile hangi anti-patternlerden kaçınmak gerekir? Virtual Threads, CPU ağırlıklı yükler ve "pinning"e neden olan durumlar için uygun değildir. Pinning, bir Virtual Thread'in bloklanmaya rağmen taşıyıcısına bağlı kalmasıyla ortaya çıkar ve sanallaştırmanın faydalarını ortadan kaldırır. ```java // AntiPatterns.java // Examples of cases to avoid @Service public class AntiPatterns { // ANTI-PATTERN 1: CPU-intensive computation // Virtual Threads provide no benefit here public BigInteger computeFactorial(int n) { // 100% CPU, no I/O, no unmounting possible BigInteger result = BigInteger.ONE; for (int i = 2; i <= n; i++) { result = result.multiply(BigInteger.valueOf(i)); } return result; // Carrier is monopolized throughout } // ANTI-PATTERN 2: Synchronized causes pinning private final Object lock = new Object(); public void pinnedOperation() { synchronized (lock) { // PINNING: VT stays on carrier performDatabaseQuery(); // Unmounting doesn't happen! } } // SOLUTION: Use ReentrantLock private final ReentrantLock reentrantLock = new ReentrantLock(); public void unpinnedOperation() { reentrantLock.lock(); try { performDatabaseQuery(); // Unmounting possible } finally { reentrantLock.unlock(); } } } ``` Pinning, kaynak açısından Virtual Thread'i Platform Thread'e dönüştürür. Başlıca nedenler `synchronized` blokları ve yerel JNI çağrılarıdır. `ReentrantLock`'a geçiş ilk durumu çözer. ### Soru 6: Bir uygulamada pinning nasıl tespit edilir? JVM, pinning vakalarını tespit etmek için tanılama seçenekleri sunar. Bu bilgi, Virtual Threads'e geçişte kritik öneme sahiptir; çünkü pinning performansı iyileştirmek yerine bozabilir. ```java // PinningDiagnostics.java // Configuration and pinning detection public class PinningDiagnostics { // JVM option to log pinning // -Djdk.tracePinnedThreads=full (detailed) // -Djdk.tracePinnedThreads=short (summary) // Example code causing pinning public void demonstratePinning() { Thread.ofVirtual().start(() -> { synchronized (this) { // This log will appear with tracePinnedThreads enabled try { Thread.sleep(100); // Pinned during sleep! } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }); } } ``` ```bash # Typical output with -Djdk.tracePinnedThreads=full Thread[#23,VirtualThread[#1]/runnable@ForkJoinPool-1-worker-1,5,CarrierThreads] java.base/java.lang.VirtualThread$VThreadContinuation.onPinned(VirtualThread.java:183) java.base/java.lang.VirtualThread.parkOnCarrierThread(VirtualThread.java:661) com.example.PinningDiagnostics.demonstratePinning(PinningDiagnostics.java:15) ``` Pinning loglarının analizi düzeltilmesi gereken sıcak noktaları ortaya çıkarır. Sık pinning yaşayan bir uygulama Virtual Threads'i tam kullanamaz ve Platform Threads'ten bile yavaş olabilir. ## Performans Karşılaştırmaları ### Soru 7: Virtual Threads'ten ne kadar performans kazancı beklenebilir? Karşılaştırmalar, tipik I/O ağırlıklı uygulamalarda kayda değer iyileşmeler gösterir. Kazançlar I/O süresi / CPU süresi oranına ve gerekli eşzamanlılık seviyesine bağlıdır. ```java // BenchmarkController.java // Endpoint for measuring performance @RestController @RequestMapping("/api/benchmark") public class BenchmarkController { private final ExternalApiClient apiClient; // Simulation of a typical endpoint with external calls @GetMapping("/user/{id}") public ResponseEntity getUser(@PathVariable Long id) { // Three sequential I/O calls UserInfo info = apiClient.fetchUserInfo(id); // ~50ms List orders = apiClient.fetchOrders(id); // ~80ms CreditScore score = apiClient.fetchCreditScore(id); // ~100ms return ResponseEntity.ok(new UserData(info, orders, score)); } } ``` ```text # Benchmark results - 10,000 concurrent requests # Configuration: 8 cores, 16GB RAM, simulated latency 230ms/request Platform Threads (pool 200): - Throughput: 850 req/s - P99 latency: 1250ms - Heap memory: 2.1GB Virtual Threads: - Throughput: 4200 req/s - P99 latency: 280ms - Heap memory: 850MB Gain: 5x throughput, 4.5x P99 latency reduction ``` Virtual Threads ayrıca bellek kullanımını da düşürür; her thread Platform Thread'in yaklaşık 1MB'sı yerine yalnızca birkaç KB ayırır. ### Soru 8: Virtual Threads ile bağlantı havuzları nasıl yapılandırılır? Bağlantı havuzları (HikariCP, Lettuce) Virtual Threads ile birlikte yeni darboğaz haline gelir. 10 bağlantılı bir havuz, milyonlarca Virtual Thread olsa bile aynı anda 10 DB sorgusuyla sınırlıdır. ```yaml # application.yml # HikariCP configuration optimized for Virtual Threads spring: datasource: hikari: # More connections since Virtual Threads allow more concurrency maximum-pool-size: 50 minimum-idle: 10 # Shorter timeout due to more concurrent requests connection-timeout: 5000 # Fast validation validation-timeout: 3000 # Redis with Lettuce - already async-friendly data: redis: lettuce: pool: max-active: 50 max-idle: 20 ``` ```java // ConnectionPoolMonitor.java // Pool monitoring to avoid contention @Component public class ConnectionPoolMonitor { private final HikariDataSource dataSource; @Scheduled(fixedRate = 10000) public void logPoolStats() { HikariPoolMXBean pool = dataSource.getHikariPoolMXBean(); log.info("Pool stats - Active: {}, Idle: {}, Waiting: {}", pool.getActiveConnections(), pool.getIdleConnections(), pool.getThreadsAwaitingConnection()); // Alert if too many threads waiting if (pool.getThreadsAwaitingConnection() > 100) { log.warn("Connection pool contention detected!"); } } } ``` Havuz boyutu Virtual Thread sayısına değil veritabanı kapasitesine bağlıdır. Standart bir PostgreSQL yaklaşık 100-200 aktif bağlantıyı destekler. ### Soru 9: Virtual Threads etkisi Micrometer ile nasıl ölçülür? Micrometer ve Spring Boot Actuator, Virtual Threads'in etkinliğini değerlendirmek için temel metrikler sağlar. Bu metrikler kazançları doğrular ve potansiyel sorunları açığa çıkarır. ```java // VirtualThreadMetrics.java // Custom metrics for Virtual Threads @Component public class VirtualThreadMetrics { private final MeterRegistry registry; private final AtomicLong activeVirtualThreads = new AtomicLong(0); @PostConstruct public void registerMetrics() { // Active Virtual Threads counter Gauge.builder("virtual.threads.active", activeVirtualThreads, AtomicLong::get) .description("Number of active virtual threads") .register(registry); // JVM metrics for carriers Gauge.builder("virtual.threads.carriers", this::getCarrierCount) .description("Number of carrier threads") .register(registry); } private double getCarrierCount() { // Gets the ForkJoinPool carrier count return ForkJoinPool.commonPool().getPoolSize(); } // Interceptor to trace requests public void trackVirtualThread(Runnable task) { activeVirtualThreads.incrementAndGet(); try { task.run(); } finally { activeVirtualThreads.decrementAndGet(); } } } ``` ```yaml # application.yml # Metrics exposure management: endpoints: web: exposure: include: health,metrics,prometheus metrics: tags: application: ${spring.application.name} ``` Metriklerin analizi Virtual Threads / Carriers oranını ortaya koyar ve bağlantı havuzundaki çekişme dönemlerini saptar. ## Geçiş ve Uyumluluk ### Soru 10: Hangi kütüphaneler Virtual Threads ile uyumludur? Uyumluluk, `synchronized` bloklarının ve yerel çağrıların kullanımına bağlıdır. Spring ekosistemi büyük ölçüde uyumludur, ancak bazı kütüphaneler belirli sürümler gerektirir. ```java // CompatibilityCheck.java // Checking dependency compatibility @Configuration public class CompatibilityCheck { // Compatible libraries (recommended versions) // - Spring Boot 3.2+ (native support) // - HikariCP 5.1+ (ReentrantLock instead of synchronized) // - Lettuce 6.3+ (non-blocking I/O) // - Jackson 2.16+ (no synchronized) // Libraries requiring attention // - JDBC drivers: check version // - Some legacy HTTP clients @Bean public CommandLineRunner checkCompatibility() { return args -> { // Log versions for audit log.info("Java version: {}", System.getProperty("java.version")); log.info("Virtual threads available: {}", Thread.ofVirtual() != null); // Support verification if (Runtime.version().feature() < 21) { throw new IllegalStateException( "Java 21+ required for Virtual Threads"); } }; } } ``` Mevcut çoğu modern framework `ReentrantLock`'a geçmiştir. Eski bağımlılıklar için `-Djdk.tracePinnedThreads=short` ile yapılan yük testi pinning sorunlarını ortaya çıkarır. ### Soru 11: Virtual Threads'e nasıl aşamalı olarak geçilir? Aşamalı geçiş, üretimi riske atmadan kazançları doğrulamayı ve sorunları tespit etmeyi sağlar. Önerilen strateji, Virtual Threads endpoint'lerini geleneksel olanlardan ayırır. ```java // DualExecutorConfig.java // Configuration for progressive migration @Configuration public class DualExecutorConfig { // Traditional executor for legacy endpoints @Bean("platformExecutor") public ExecutorService platformExecutor() { return Executors.newFixedThreadPool(200); } // Virtual Threads executor for new endpoints @Bean("virtualExecutor") public ExecutorService virtualExecutor() { return Executors.newVirtualThreadPerTaskExecutor(); } } @RestController @RequestMapping("/api/v2") public class MigratedController { @Qualifier("virtualExecutor") private final ExecutorService executor; // Endpoint migrated to Virtual Threads @GetMapping("/users/{id}") public CompletableFuture getUser(@PathVariable Long id) { return CompletableFuture.supplyAsync(() -> { // Business code unchanged return userService.findById(id); }, executor); } } ``` ```yaml # application.yml # Feature flag for migration features: virtual-threads: enabled: true endpoints: - /api/v2/** - /api/reports/** # Disable for quick rollback # features.virtual-threads.enabled: false ``` > **Dikkat** > > Tüm bağımlılıkları test etmeden Virtual Threads'i global olarak etkinleştirmemek gerekir. Pinning performansı önemli ölçüde düşürebilir. ### Soru 12: Üretime almadan önce hangi testler yapılmalıdır? Virtual Threads doğrulaması yük testleri, pinning testleri ve uyumluluk testleri gerektirir. Bu testlerin gerçekçi üretim koşullarını taklit etmesi şarttır. ```java // VirtualThreadLoadTest.java // Load test with JUnit and Virtual Threads @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) class VirtualThreadLoadTest { @LocalServerPort private int port; @Test void shouldHandleHighConcurrency() throws Exception { int concurrentRequests = 5000; CountDownLatch latch = new CountDownLatch(concurrentRequests); AtomicInteger successCount = new AtomicInteger(0); AtomicInteger errorCount = new AtomicInteger(0); HttpClient client = HttpClient.newHttpClient(); // Launch 5000 simultaneous requests try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { for (int i = 0; i < concurrentRequests; i++) { executor.submit(() -> { try { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("http://localhost:" + port + "/api/test")) .build(); HttpResponse response = client.send( request, BodyHandlers.ofString()); if (response.statusCode() == 200) { successCount.incrementAndGet(); } else { errorCount.incrementAndGet(); } } catch (Exception e) { errorCount.incrementAndGet(); } finally { latch.countDown(); } }); } } latch.await(60, TimeUnit.SECONDS); // Performance assertions assertThat(successCount.get()).isGreaterThan(4900); // >98% success assertThat(errorCount.get()).isLessThan(100); } } ``` Testler çekişme senaryolarını (doymuş bağlantı havuzu), zaman aşımlarını ve dakikalarca süren sürdürülmüş yükü kapsamalıdır. ## İleri Seviye Sorular ### Soru 13: Virtual Threads ile Structured Concurrency nasıl etkileşir? Structured Concurrency (JEP 453) eşzamanlı görevlerin aynı yaşam döngüsünü paylaşmasını sağlayarak Virtual Threads'i tamamlar. Bu yaklaşım hata yönetimini ve iptal işlemlerini kolaylaştırır. ```java // StructuredConcurrencyExample.java // Combining Virtual Threads + Structured Concurrency public class StructuredConcurrencyExample { public UserDashboard fetchDashboard(Long userId) throws Exception { // StructuredTaskScope ensures all tasks complete together try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { // Three parallel tasks on Virtual Threads Subtask profileTask = scope.fork(() -> userService.getProfile(userId)); Subtask> notifTask = scope.fork(() -> notificationService.getRecent(userId)); Subtask balanceTask = scope.fork(() -> accountService.getBalance(userId)); // Wait for all tasks or fail on first error scope.join(); scope.throwIfFailed(); // All tasks succeeded - safe aggregation return new UserDashboard( profileTask.get(), notifTask.get(), balanceTask.get() ); } // If one task fails, others are automatically cancelled } } ``` Structured Concurrency thread sızıntılarını engeller ve hata ayıklamayı kolaylaştırır; çünkü çağrı yığını kodun mantıksal yapısını yansıtır. ### Soru 14: Virtual Threads ile reaktif programlama arasındaki fark nedir? Her iki yaklaşım da aynı sorunu (I/O verimliliği) çözer ancak farklı programlama modelleri kullanır. Virtual Threads klasik imperatif kod yazmaya izin verirken reaktif yaklaşım stream'ler şeklinde yeniden yazımı zorunlu kılar. ```java // ComparisonService.java // Same logic: imperative vs reactive @Service public class ComparisonService { // VIRTUAL THREADS APPROACH: classic imperative code // Easy to read, debug, and maintain public UserData getUserDataImperative(Long id) { User user = userRepository.findById(id).orElseThrow(); List orders = orderRepository.findByUserId(id); PaymentInfo payment = paymentService.getInfo(id); return new UserData(user, orders, payment); } // REACTIVE APPROACH: streams and operators // More complex but native backpressure public Mono getUserDataReactive(Long id) { return userRepository.findById(id) .zipWith(orderRepository.findByUserId(id).collectList()) .zipWith(paymentService.getInfo(id)) .map(tuple -> new UserData( tuple.getT1().getT1(), tuple.getT1().getT2(), tuple.getT2() )); } } ``` | Kriter | Virtual Threads | Reaktif | |--------|-----------------|---------| | Öğrenme eğrisi | Düşük | Yüksek | | Hata ayıklama | Klasik stack trace | Karmaşık | | Backpressure | Manuel | Yerel | | Ekosistem | Büyüyen | Olgun | | Eski kod geçişi | Basit | Yeniden yazma | Yeni uygulamalar ve göçler için Virtual Threads önerilir. Reaktif programlama, gelişmiş backpressure gerektiren senaryolarda hâlâ değerlidir. ### Soru 15: ThreadLocal Virtual Threads ile nasıl yönetilir? ThreadLocal Virtual Threads ile çalışır, ancak her örnek için bellek tüketir. Scoped Values (JEP 446) bağlam paylaşımı için daha verimli bir alternatif sunar. ```java // ThreadLocalVsScopedValue.java // Comparing context approaches public class ThreadLocalVsScopedValue { // CLASSIC APPROACH: ThreadLocal // Works but expensive with millions of VT private static final ThreadLocal requestContext = new ThreadLocal<>(); public void processWithThreadLocal(Request request) { requestContext.set(new RequestContext(request.getTraceId())); try { // Context accessible everywhere in the thread processRequest(); } finally { requestContext.remove(); // Important to prevent leaks } } // MODERN APPROACH: ScopedValue (Java 21+) // More efficient, immutable, explicit scope private static final ScopedValue CONTEXT = ScopedValue.newInstance(); public void processWithScopedValue(Request request) { ScopedValue.where(CONTEXT, new RequestContext(request.getTraceId())) .run(() -> { // Context accessible within this scope processRequest(); // No cleanup needed - automatic at scope end }); } private void processRequest() { // Context access String traceId = CONTEXT.isBound() ? CONTEXT.get().getTraceId() : "unknown"; log.info("Processing with trace: {}", traceId); } } ``` Yeni geliştirmeler için ScopedValues önerilir. ThreadLocal kullanan eski kod için aşamalı geçiş yapmak makuldür. ## Sonuç Virtual Threads, basit ve yüksek performanslı kod yazımına olanak tanıyarak Java backend geliştirmesini dönüştürür. Önemli noktalar: **Temeller:** - Etkinleştirme: `spring.threads.virtual.enabled=true` - I/O ağırlıklı yükler için ideal (REST, BD, dosyalar) - Yoğun CPU hesaplamaları için kaçınılmalı **Performans:** - Bağlantı havuzlarını doğru boyutlandırın (yeni darboğaz) - Pinning'i `-Djdk.tracePinnedThreads` ile izleyin - `synchronized`'ı `ReentrantLock`'a taşıyın **Geçiş:** - Endpoint grupları halinde aşamalı test edin - Bağımlılıkların uyumluluğunu doğrulayın - Paralel işlemler için Structured Concurrency'yi kullanın Virtual Threads bilgisi, modern uygulamaların performans zorluklarını anlayan adayları öne çıkarır. Bu kavramlar artık Spring Boot teknik mülakatlarında vazgeçilmezdir. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/tr/blog/spring-boot/spring-boot-virtual-threads-interview