# Spring Boot 3.4 Virtual Threads: คำถามสัมภาษณ์และ Benchmark ประสิทธิภาพ > เชี่ยวชาญ Java 21 Virtual Threads ด้วย Spring Boot 3.4: 15 คำถามสัมภาษณ์ Benchmark ประสิทธิภาพ และรูปแบบการย้ายระบบสำหรับสอบสัมภาษณ์เทคนิค - 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 และ Spring Boot 3.4 รองรับอย่างเป็น native คุณสมบัติของ Project Loom นี้เปลี่ยนวิธีจัดการ concurrency ในแอปพลิเคชัน backend อย่างสิ้นเชิง การสัมภาษณ์เทคนิคในปัจจุบันจะวัดความเข้าใจกลไกภายใน กรณีใช้งานที่เหมาะสม และข้อผิดพลาดที่ควรหลีกเลี่ยง > **คำแนะนำในการเตรียมตัว** > > ผู้สัมภาษณ์จะแยกผู้สมัครที่เข้าใจ Virtual Threads อย่างแท้จริงออกจากผู้ที่ใช้งานแบบไม่เข้าใจ การรู้ว่าเมื่อใดไม่ควรใช้นั้นสำคัญเทียบเท่ากับการรู้ประโยชน์ของมัน ## พื้นฐาน Virtual Threads ### คำถามที่ 1: Virtual Thread คืออะไรและต่างจาก Platform Thread อย่างไร Virtual Thread คือ thread แบบเบาที่ JVM เป็นผู้บริหารจัดการ ไม่ใช่ระบบปฏิบัติการ ต่างจาก Platform Threads (thread แบบดั้งเดิม) ตรงที่ Virtual Threads ไม่ได้แมปตรงกับ thread ของ OS โดย JVM สามารถสร้างมันได้นับล้านโดยใช้หน่วยความจำเพียงเล็กน้อย ```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(); } } } ``` ความแตกต่างที่สำคัญที่สุดอยู่ที่พฤติกรรมเมื่อเกิดการ blocking เมื่อ Platform Thread ถูก block (I/O, sleep) มันจะยังผูกอยู่กับ thread ของ OS ขณะที่ Virtual Thread จะถอดตัวเอง (unmount) ออกจาก carrier thread เพื่อปล่อยให้ Virtual Threads ตัวอื่นใช้ carrier นั้นได้ ### คำถามที่ 2: เปิดใช้งาน Virtual Threads ใน Spring Boot 3.4 อย่างไร Spring Boot 3.4 ลดการเปิดใช้งาน Virtual Threads ให้เหลือเพียง property การตั้งค่าเดียว เฟรมเวิร์กทั้งหมดจะปรับตัวอัตโนมัติ ทั้ง Tomcat, REST controller และการเรียกแบบ blocking ได้รับประโยชน์จากการปรับแต่งนี้ทันที ```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()); }; } } ``` การเปิดใช้งานเปลี่ยนพฤติกรรมของ Tomcat แทนที่จะใช้ thread pool ขนาดคงที่ ทุก request จะได้รับ Virtual Thread เป็นของตัวเอง วิธีการนี้ขจัดคอขวดของ thread pool แบบเดิม ### คำถามที่ 3: อธิบายแนวคิด "mounting" และ "unmounting" Mounting หมายถึงการผูก Virtual Thread เข้ากับ carrier thread (Platform Thread) ส่วน unmounting เกิดขึ้นระหว่างการดำเนินการแบบ blocking โดยจะปล่อย carrier ให้ Virtual Threads อื่นได้ใช้ กลไกนี้ทำให้ใช้ทรัพยากร CPU ได้อย่างมีประสิทธิภาพสูงสุด ```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(); } } ``` กลไกนี้โปร่งใสสำหรับนักพัฒนา โค้ดยังเขียนในรูปแบบ imperative คลาสสิก แต่ JVM จะปรับการใช้ carrier ให้อัตโนมัติ pool ที่มี carrier เพียงไม่กี่ตัวก็สามารถรองรับ Virtual Thread นับล้านได้ > **หมายเหตุทางเทคนิค** > > จำนวน carrier thread โดยปริยายจะเท่ากับจำนวน core ของ CPU JVM ปรับขนาด pool นี้แบบไดนามิกผ่าน ForkJoinPool ## กรณีใช้งานและ Anti-pattern ### คำถามที่ 4: Virtual Threads ให้ผลด้านประสิทธิภาพเมื่อใด Virtual Threads เปล่งประกายกับงาน I/O-bound เช่น การเรียก REST ภายนอก การ query ฐานข้อมูล หรือการอ่านไฟล์ ปฏิบัติการเหล่านี้ใช้เวลาส่วนใหญ่รอ ในช่วงเวลาดังกล่าว Virtual Thread จะปล่อย carrier ของตัวเอง ```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); } } ``` ผลตอบแทนมาจากการรองรับ request พร้อมกันได้มากขึ้น Platform Threads จำนวน 200 และ request ละ 100ms ให้ throughput สูงสุดเพียง 2,000 req/s ในขณะที่ Virtual Threads สามารถไปถึงกว่า 50,000 req/s บนเครื่องเดียวกัน ### คำถามที่ 5: ควรหลีกเลี่ยง Anti-pattern ใดเมื่อใช้ Virtual Threads Virtual Threads ไม่เหมาะกับงาน CPU-bound หรือสถานการณ์ที่ทำให้เกิด "pinning" pinning เกิดขึ้นเมื่อ Virtual Thread ยังคงผูกกับ carrier แม้จะ block อยู่ ทำให้ประโยชน์ของการทำ virtualization หายไป ```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 เปลี่ยน Virtual Thread ให้กลายเป็น Platform Thread สาเหตุหลักคือบล็อก `synchronized` และการเรียก JNI แบบ native การเปลี่ยนไปใช้ `ReentrantLock` แก้ปัญหาแรกได้ ### คำถามที่ 6: ตรวจจับ pinning ในแอปพลิเคชันได้อย่างไร JVM มีตัวเลือกการวินิจฉัยเพื่อระบุกรณี pinning ข้อมูลนี้สำคัญมากในการย้ายระบบไป Virtual Threads เพราะ pinning อาจทำให้ประสิทธิภาพแย่ลงแทนที่จะดีขึ้น ```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) ``` การวิเคราะห์ log ของ pinning จะบ่งชี้จุดร้อนที่ต้องแก้ไข แอปพลิเคชันที่เกิด pinning บ่อยจะใช้ประโยชน์จาก Virtual Threads ได้ไม่เต็มที่ และอาจทำงานช้ากว่าการใช้ Platform Threads ด้วยซ้ำ ## Benchmark ประสิทธิภาพ ### คำถามที่ 7: คาดหวังการเพิ่มประสิทธิภาพได้แค่ไหนจาก Virtual Threads Benchmark แสดงการปรับปรุงอย่างมีนัยสำคัญในแอปพลิเคชัน I/O-bound ทั่วไป ผลตอบแทนขึ้นกับสัดส่วนระหว่างเวลา I/O กับเวลา CPU และระดับ concurrency ที่ต้องการ ```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 ยังลดการใช้หน่วยความจำลง เนื่องจากแต่ละ thread จองหน่วยความจำเพียงไม่กี่ KB แทนที่จะเป็นประมาณ 1 MB ของ Platform Thread ### คำถามที่ 8: ควรตั้งค่า connection pool อย่างไรเมื่อใช้ Virtual Threads Connection pool (HikariCP, Lettuce) จะกลายเป็นคอขวดใหม่เมื่อใช้ Virtual Threads pool ที่มี 10 connection จะจำกัด query ฐานข้อมูลพร้อมกันไว้ที่ 10 ครั้ง แม้จะมี Virtual Thread นับล้านก็ตาม ```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!"); } } } ``` ขนาดของ pool ขึ้นอยู่กับความสามารถของฐานข้อมูล ไม่ใช่จำนวน Virtual Threads PostgreSQL มาตรฐานรองรับการเชื่อมต่อใช้งานพร้อมกันราว 100-200 connection ### คำถามที่ 9: วัดผลกระทบของ Virtual Threads ด้วย Micrometer ได้อย่างไร Micrometer และ Spring Boot Actuator ให้ตัวชี้วัดสำคัญสำหรับประเมินประสิทธิภาพของ Virtual Threads ตัวชี้วัดเหล่านี้ยืนยันผลตอบแทนและเปิดเผยปัญหาที่อาจซ่อนอยู่ ```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} ``` การวิเคราะห์ตัวชี้วัดเผยให้เห็นสัดส่วนระหว่าง Virtual Threads และ Carriers พร้อมระบุช่วงเวลาที่เกิด contention บน connection pool ## การย้ายระบบและความเข้ากันได้ ### คำถามที่ 10: ไลบรารีใดที่เข้ากันได้กับ Virtual Threads ความเข้ากันได้ขึ้นอยู่กับการใช้บล็อก `synchronized` และการเรียกแบบ native ระบบนิเวศของ Spring เข้ากันได้ในวงกว้าง แต่ไลบรารีบางตัวยังต้องการเวอร์ชันเฉพาะ ```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"); } }; } } ``` เฟรมเวิร์กสมัยใหม่ส่วนใหญ่ย้ายมาใช้ `ReentrantLock` แล้ว สำหรับ dependency รุ่นเก่า การทดสอบโหลดด้วย `-Djdk.tracePinnedThreads=short` จะช่วยเปิดเผยปัญหา pinning ### คำถามที่ 11: จะย้ายระบบไป Virtual Threads อย่างค่อยเป็นค่อยไปได้อย่างไร การย้ายแบบค่อยเป็นค่อยไปทำให้สามารถยืนยันผลตอบแทนและตรวจพบปัญหาได้โดยไม่กระทบต่อ production กลยุทธ์ที่แนะนำคือแยก endpoint ที่ใช้ Virtual Threads ออกจาก endpoint แบบเดิม ```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 ``` > **ข้อควรระวัง** > > ไม่ควรเปิด Virtual Threads แบบ global ก่อนที่จะทดสอบ dependency ทั้งหมด pinning อาจทำให้ประสิทธิภาพแย่ลงอย่างมีนัยสำคัญ ### คำถามที่ 12: ต้องทดสอบอะไรบ้างก่อนนำขึ้น production การตรวจสอบ Virtual Threads ต้องอาศัยการทดสอบโหลด การทดสอบ pinning และการทดสอบความเข้ากันได้ การทดสอบเหล่านี้ต้องจำลองสภาพ production ตามจริง ```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); } } ``` การทดสอบควรครอบคลุมสถานการณ์ contention (connection pool ตัน) timeouts และโหลดต่อเนื่องเป็นเวลาหลายนาที ## คำถามขั้นสูง ### คำถามที่ 13: Virtual Threads ทำงานร่วมกับ Structured Concurrency อย่างไร Structured Concurrency (JEP 453) เสริม Virtual Threads ด้วยการรับประกันว่า task ที่ทำงานพร้อมกันมี lifecycle ร่วมกัน วิธีการนี้ทำให้การจัดการข้อผิดพลาดและการยกเลิกงานง่ายขึ้น ```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 leak และทำให้การ debug ง่ายขึ้น เพราะ call stack สะท้อนโครงสร้างเชิงตรรกะของโค้ด ### คำถามที่ 14: Virtual Threads ต่างจาก reactive programming อย่างไร ทั้งสองแนวทางแก้ปัญหาเดียวกัน (ประสิทธิภาพ I/O) แต่ใช้รูปแบบการเขียนโปรแกรมต่างกัน Virtual Threads ให้เขียนโค้ดแบบ imperative คลาสสิก ในขณะที่ reactive ต้องเขียนใหม่ในรูปแบบ stream ```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() )); } } ``` | เกณฑ์ | Virtual Threads | Reactive | |-------|-----------------|----------| | เส้นโค้งการเรียนรู้ | ต่ำ | สูง | | Debug | Stack trace แบบคลาสสิก | ซับซ้อน | | Backpressure | ทำเอง | Native | | ระบบนิเวศ | กำลังเติบโต | สมบูรณ์ | | ย้าย legacy | ง่าย | เขียนใหม่ | Virtual Threads แนะนำสำหรับแอปใหม่และการย้ายระบบ ส่วน reactive ยังเหมาะกับงานที่ต้องการ backpressure ซับซ้อน ### คำถามที่ 15: จัดการ ThreadLocal กับ Virtual Threads อย่างไร ThreadLocal ใช้ได้กับ Virtual Threads แต่กินหน่วยความจำต่อ instance Scoped Values (JEP 446) เป็นทางเลือกที่มีประสิทธิภาพมากกว่าในการแชร์ context ```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); } } ``` ScopedValues แนะนำสำหรับงานพัฒนาใหม่ ส่วนโค้ดเก่าที่ใช้ ThreadLocal ควรย้ายระบบทีละน้อย ## บทสรุป Virtual Threads เปลี่ยนแปลงการพัฒนา backend ของ Java ด้วยการเขียนโค้ดแบบเรียบง่ายแต่ประสิทธิภาพสูง ประเด็นสำคัญ: **พื้นฐาน:** - เปิดใช้งานด้วย `spring.threads.virtual.enabled=true` - เหมาะกับงาน I/O-bound (REST, DB, ไฟล์) - หลีกเลี่ยงงานที่ใช้ CPU หนัก **ประสิทธิภาพ:** - กำหนดขนาด connection pool ให้เหมาะสม (คอขวดใหม่) - ติดตาม pinning ด้วย `-Djdk.tracePinnedThreads` - ย้าย `synchronized` ไปใช้ `ReentrantLock` **การย้ายระบบ:** - ทดสอบเป็นกลุ่ม endpoint อย่างค่อยเป็นค่อยไป - ตรวจสอบความเข้ากันได้ของ dependency - ใช้ Structured Concurrency กับงานขนาน ความเชี่ยวชาญใน Virtual Threads ทำให้ผู้สมัครที่เข้าใจความท้าทายด้านประสิทธิภาพของแอปสมัยใหม่โดดเด่นออกมา แนวคิดเหล่านี้กลายเป็นสิ่งจำเป็นในการสัมภาษณ์ Spring Boot ในปัจจุบันแล้ว --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/th/blog/spring-boot/spring-boot-virtual-threads-interview