Spring Boot 3.4 Virtual Threads: Câu Hỏi Phỏng Vấn và Benchmark Hiệu Năng

Làm chủ Virtual Threads của Java 21 với Spring Boot 3.4: 15 câu hỏi phỏng vấn, benchmark hiệu năng và mẫu di chuyển để vượt qua các buổi phỏng vấn kỹ thuật.

Spring Boot 3.4 Virtual Threads: câu hỏi phỏng vấn và benchmark hiệu năng

Virtual Threads là một trong những cải tiến quan trọng nhất của Java 21, và Spring Boot 3.4 hỗ trợ chúng một cách native. Tính năng thuộc Project Loom này thay đổi cách xử lý concurrency trong các ứng dụng backend. Các buổi phỏng vấn kỹ thuật ngày nay đánh giá việc nắm vững cơ chế bên trong, các tình huống sử dụng phù hợp và những bẫy phổ biến cần tránh.

Mẹo Chuẩn Bị

Người phỏng vấn phân biệt rõ giữa ứng viên thực sự hiểu Virtual Threads và những người dùng chúng một cách máy móc. Biết khi nào KHÔNG nên dùng quan trọng không kém việc biết lợi ích của chúng.

Nền Tảng Virtual Threads

Câu hỏi 1: Virtual Thread là gì và khác Platform Thread ra sao?

Virtual Thread là một thread nhẹ do JVM quản lý chứ không phải hệ điều hành. Khác với Platform Threads (thread truyền thống), Virtual Threads không ánh xạ trực tiếp tới thread của OS. JVM có thể tạo ra hàng triệu Virtual Thread với mức tiêu hao bộ nhớ tối thiểu.

VirtualThreadDemo.javajava
// 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();
        }
    }
}

Khác biệt cốt lõi nằm ở hành vi khi blocking. Khi Platform Thread bị blocking (I/O, sleep), nó vẫn dính chặt vào thread của OS. Trong khi đó Virtual Thread "unmount" khỏi carrier thread, giải phóng carrier cho các Virtual Thread khác.

Câu hỏi 2: Cách bật Virtual Threads trong Spring Boot 3.4?

Spring Boot 3.4 thu gọn việc bật Virtual Threads xuống còn một thuộc tính cấu hình duy nhất. Toàn bộ framework tự động thích ứng: Tomcat, REST controller và các lời gọi blocking lập tức hưởng lợi từ tối ưu này.

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
WebConfig.javajava
// 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());
        };
    }
}

Việc bật Virtual Threads thay đổi hành vi của Tomcat: thay vì pool thread cố định, mỗi request nhận một Virtual Thread riêng. Cách tiếp cận này loại bỏ nút thắt cổ chai cố hữu của thread pool.

Câu hỏi 3: Giải thích khái niệm "mounting" và "unmounting"

Mounting là việc gắn Virtual Thread vào carrier thread (Platform Thread). Unmounting xảy ra trong các thao tác blocking và giải phóng carrier cho Virtual Thread khác. Cơ chế này cho phép tận dụng tối đa tài nguyên CPU.

MountingDemo.javajava
// 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();
    }
}

Cơ chế này hoàn toàn trong suốt với lập trình viên. Code vẫn được viết theo phong cách imperative cổ điển, song JVM tự tối ưu việc dùng carrier. Một pool nhỏ vài carrier có thể phục vụ hàng triệu Virtual Thread.

Ghi Chú Kỹ Thuật

Số lượng carrier thread mặc định bằng số core CPU. JVM điều chỉnh pool này một cách động thông qua ForkJoinPool.

Tình Huống Sử Dụng và Anti-pattern

Câu hỏi 4: Khi nào Virtual Threads mang lại cải thiện hiệu năng?

Virtual Threads tỏa sáng với khối lượng công việc I/O-bound: gọi REST bên ngoài, truy vấn database, đọc file. Những thao tác này dành phần lớn thời gian để chờ, và trong khoảng thời gian đó Virtual Thread giải phóng carrier của mình.

IOBoundService.javajava
// 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);
    }
}

Lợi ích đến từ việc xử lý số lượng request đồng thời lớn hơn nhiều. Với 200 Platform Threads và request 100ms, throughput tối đa là 2.000 req/s. Với Virtual Threads, con số này có thể vượt 50.000 req/s trên cùng một máy.

Câu hỏi 5: Anti-pattern nào cần tránh khi dùng Virtual Threads?

Virtual Threads không phù hợp với khối lượng CPU-bound hay các tình huống gây "pinning". Pinning xảy ra khi Virtual Thread vẫn dính vào carrier dù bị blocking, làm mất đi lợi ích của ảo hóa.

AntiPatterns.javajava
// 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();
        }
    }
}

Xét về mặt tài nguyên, pinning biến Virtual Thread thành Platform Thread. Nguyên nhân chính là các khối synchronized và lời gọi native JNI. Chuyển sang ReentrantLock xử lý được trường hợp đầu tiên.

Câu hỏi 6: Làm sao phát hiện pinning trong ứng dụng?

JVM cung cấp các tùy chọn chẩn đoán để xác định các trường hợp pinning. Thông tin này rất quan trọng khi di chuyển sang Virtual Threads, vì pinning có thể làm xấu đi hiệu năng thay vì cải thiện.

PinningDiagnostics.javajava
// 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)

Phân tích log pinning sẽ chỉ ra các điểm nóng cần khắc phục. Một ứng dụng bị pinning thường xuyên sẽ không khai thác đầy đủ Virtual Threads, thậm chí còn chậm hơn so với Platform Threads.

Sẵn sàng chinh phục phỏng vấn Spring Boot?

Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.

Benchmark Hiệu Năng

Câu hỏi 7: Có thể kỳ vọng cải thiện hiệu năng tới đâu với Virtual Threads?

Các benchmark cho thấy mức cải thiện đáng kể với những ứng dụng I/O-bound điển hình. Mức cải thiện phụ thuộc vào tỉ lệ thời gian I/O so với CPU và mức concurrency cần đạt.

BenchmarkController.javajava
// 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<UserData> getUser(@PathVariable Long id) {
        // Three sequential I/O calls
        UserInfo info = apiClient.fetchUserInfo(id);        // ~50ms
        List<Order> 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 còn giảm tiêu thụ bộ nhớ vì mỗi thread chỉ chiếm vài KB thay vì khoảng 1 MB như Platform Thread.

Câu hỏi 8: Cấu hình connection pool ra sao khi dùng Virtual Threads?

Connection pool (HikariCP, Lettuce) trở thành nút thắt mới khi dùng Virtual Threads. Một pool 10 connection chỉ cho phép 10 truy vấn DB đồng thời, kể cả khi có hàng triệu 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
ConnectionPoolMonitor.javajava
// 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!");
        }
    }
}

Kích thước pool phụ thuộc vào sức chứa của database, không phụ thuộc số lượng Virtual Thread. PostgreSQL tiêu chuẩn xử lý được khoảng 100-200 connection đang hoạt động.

Câu hỏi 9: Đo lường tác động của Virtual Threads bằng Micrometer như thế nào?

Micrometer và Spring Boot Actuator cung cấp các metric thiết yếu để đánh giá hiệu quả của Virtual Threads. Những metric này xác nhận lợi ích và bộc lộ vấn đề tiềm ẩn.

VirtualThreadMetrics.javajava
// 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}

Việc phân tích metric cho thấy tỉ lệ Virtual Threads / Carriers và xác định các giai đoạn xảy ra contention trên connection pool.

Di Chuyển và Tương Thích

Câu hỏi 10: Thư viện nào tương thích với Virtual Threads?

Mức tương thích phụ thuộc vào việc dùng các khối synchronized và các lời gọi native. Hệ sinh thái Spring chủ yếu đã tương thích, nhưng một số thư viện đòi hỏi phiên bản cụ thể.

CompatibilityCheck.javajava
// 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");
            }
        };
    }
}

Phần lớn các framework hiện đại đã chuyển sang ReentrantLock. Với các phụ thuộc cũ, một bài kiểm thử tải bằng -Djdk.tracePinnedThreads=short sẽ chỉ ra các vấn đề pinning.

Câu hỏi 11: Làm thế nào để di chuyển dần dần sang Virtual Threads?

Một cuộc di chuyển từng bước cho phép xác nhận lợi ích và phát hiện vấn đề mà không gây rủi ro cho production. Chiến lược được khuyến nghị là tách các endpoint dùng Virtual Threads khỏi endpoint truyền thống.

DualExecutorConfig.javajava
// 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<User> 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
Lưu Ý

Không nên bật Virtual Threads toàn cục trước khi kiểm thử mọi phụ thuộc. Pinning có thể làm hiệu năng giảm đáng kể.

Câu hỏi 12: Cần kiểm thử gì trước khi triển khai production?

Việc kiểm chứng Virtual Threads đòi hỏi kiểm thử tải, kiểm thử pinning và kiểm thử tương thích. Các bài kiểm thử này phải mô phỏng điều kiện production thực tế.

VirtualThreadLoadTest.javajava
// 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<String> 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);
    }
}

Kiểm thử cần bao quát các kịch bản contention (connection pool quá tải), timeout và tải kéo dài trong nhiều phút.

Câu Hỏi Nâng Cao

Câu hỏi 13: Virtual Threads tương tác với Structured Concurrency thế nào?

Structured Concurrency (JEP 453) bổ sung cho Virtual Threads bằng cách bảo đảm các task đồng thời chia sẻ cùng vòng đời. Cách tiếp cận này đơn giản hóa việc xử lý lỗi và hủy bỏ.

StructuredConcurrencyExample.javajava
// 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<UserProfile> profileTask = scope.fork(() ->
                userService.getProfile(userId));

            Subtask<List<Notification>> notifTask = scope.fork(() ->
                notificationService.getRecent(userId));

            Subtask<AccountBalance> 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 ngăn ngừa rò rỉ thread và làm việc debug dễ hơn vì call stack phản ánh đúng cấu trúc logic của code.

Câu hỏi 14: Khác biệt giữa Virtual Threads và lập trình reactive là gì?

Cả hai cách tiếp cận đều giải quyết cùng một vấn đề (hiệu quả I/O) nhưng theo các mô hình lập trình khác nhau. Virtual Threads cho phép viết code imperative cổ điển, trong khi reactive đòi hỏi viết lại theo dạng stream.

ComparisonService.javajava
// 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<Order> 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<UserData> 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()
            ));
    }
}

| Tiêu chí | Virtual Threads | Reactive | |----------|-----------------|----------| | Đường cong học | Thấp | Cao | | Debug | Stack trace cổ điển | Phức tạp | | Backpressure | Thủ công | Native | | Hệ sinh thái | Đang phát triển | Trưởng thành | | Di chuyển legacy | Dễ | Viết lại |

Virtual Threads được khuyên dùng cho ứng dụng mới và việc di chuyển. Reactive vẫn phù hợp khi cần backpressure tinh vi.

Câu hỏi 15: Quản lý ThreadLocal cùng Virtual Threads ra sao?

ThreadLocal vẫn dùng được với Virtual Threads nhưng tiêu tốn bộ nhớ cho mỗi instance. Scoped Values (JEP 446) là lựa chọn hiệu quả hơn để chia sẻ context.

ThreadLocalVsScopedValue.javajava
// Comparing context approaches
public class ThreadLocalVsScopedValue {

    // CLASSIC APPROACH: ThreadLocal
    // Works but expensive with millions of VT
    private static final ThreadLocal<RequestContext> 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<RequestContext> 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 được khuyên dùng cho các phát triển mới. Với code legacy đang dùng ThreadLocal, nên di chuyển dần dần.

Sẵn sàng chinh phục phỏng vấn Spring Boot?

Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.

Kết Luận

Virtual Threads thay đổi cách phát triển backend Java khi cho phép viết code đơn giản mà vẫn hiệu năng cao. Điểm mấu chốt:

Nền tảng:

  • Bật bằng spring.threads.virtual.enabled=true
  • Lý tưởng cho khối lượng I/O-bound (REST, DB, file)
  • Tránh dùng cho các phép toán nặng CPU

Hiệu năng:

  • Thiết lập kích thước connection pool đúng (nút thắt mới)
  • Theo dõi pinning bằng -Djdk.tracePinnedThreads
  • Chuyển synchronized sang ReentrantLock

Di chuyển:

  • Kiểm thử dần theo nhóm endpoint
  • Xác nhận tính tương thích của các phụ thuộc
  • Tận dụng Structured Concurrency cho thao tác song song

Làm chủ Virtual Threads tạo nên sự khác biệt cho ứng viên hiểu rõ thách thức hiệu năng của ứng dụng hiện đại. Các khái niệm này nay đã trở thành điều bắt buộc trong phỏng vấn kỹ thuật Spring Boot.

Bắt đầu luyện tập!

Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.

Thẻ

#spring boot
#virtual threads
#java 21
#project loom
#performance

Chia sẻ

Bài viết liên quan