Spring Boot 3.4 Virtual Threads: Interviewvragen en Performance-Benchmarks

Beheers Java 21 Virtual Threads met Spring Boot 3.4: 15 interviewvragen, performance-benchmarks en migratiepatronen om technische gesprekken te slagen.

Spring Boot 3.4 Virtual Threads: interviewvragen en performance-benchmarks

Virtual Threads behoren tot de belangrijkste vernieuwingen van Java 21 en Spring Boot 3.4 integreert ze native. Dit Project Loom-onderdeel verandert de manier waarop concurrency in backend-applicaties wordt afgehandeld. Technische gesprekken toetsen tegenwoordig het begrip van interne mechanismen, geschikte use cases en veelvoorkomende valkuilen.

Voorbereidingstip

Interviewers onderscheiden kandidaten die Virtual Threads echt begrijpen van degenen die ze blind toepassen. Weten wanneer je ze NIET moet gebruiken is even belangrijk als de voordelen kennen.

Fundamenten van Virtual Threads

Vraag 1: Wat is een Virtual Thread en hoe verschilt die van een Platform Thread?

Een Virtual Thread is een lichtgewicht thread die door de JVM wordt beheerd in plaats van door het besturingssysteem. Anders dan Platform Threads (klassieke threads) worden Virtual Threads niet rechtstreeks op OS-threads afgebeeld. De JVM kan er miljoenen aanmaken met minimale geheugenbelasting.

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();
        }
    }
}

Het fundamentele verschil zit in het gedrag bij blokkering. Wanneer een Platform Thread blokkeert (I/O, sleep), blijft hij gekoppeld aan de OS-thread. Een Virtual Thread "unmount" zich daarentegen van de carrier-thread, zodat andere Virtual Threads die carrier kunnen gebruiken.

Vraag 2: Hoe activeer je Virtual Threads in Spring Boot 3.4?

Spring Boot 3.4 reduceert het inschakelen van Virtual Threads tot één configuratie-eigenschap. Het hele framework past zich automatisch aan: Tomcat, REST-controllers en blokkerende calls profiteren direct van deze optimalisatie.

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());
        };
    }
}

De activatie verandert het gedrag van Tomcat: in plaats van een vaste thread-pool krijgt elk verzoek zijn eigen Virtual Thread. Deze aanpak elimineert de klassieke bottleneck van de thread-pool.

Vraag 3: Leg de begrippen "mounting" en "unmounting" uit

Mounting verwijst naar het koppelen van een Virtual Thread aan een carrier-thread (Platform Thread). Unmounting vindt plaats tijdens blokkerende operaties en geeft de carrier vrij voor andere Virtual Threads. Dit mechanisme zorgt voor een optimale benutting van CPU-bronnen.

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();
    }
}

Dit mechanisme is transparant voor de ontwikkelaar. De code wordt in klassieke imperatieve stijl geschreven, maar de JVM optimaliseert het gebruik van carriers automatisch. Een pool van enkele carriers kan miljoenen Virtual Threads bedienen.

Technische Noot

Het aantal carrier-threads komt standaard overeen met het aantal CPU-cores. De JVM regelt deze pool dynamisch via de ForkJoinPool.

Use Cases en Anti-patterns

Vraag 4: Wanneer leveren Virtual Threads performance-winst op?

Virtual Threads schitteren bij I/O-bound werklasten: externe REST-calls, databasequery's, bestandslezingen. Deze operaties brengen het grootste deel van hun tijd wachtend door, waarin de Virtual Thread zijn carrier vrijgeeft.

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);
    }
}

De winst komt voort uit het verwerken van veel meer gelijktijdige verzoeken. Met 200 Platform Threads en verzoeken van 100ms is de maximale doorvoer 2.000 req/s. Met Virtual Threads kan dat oplopen tot 50.000+ req/s op dezelfde machine.

Vraag 5: Welke anti-patterns moeten met Virtual Threads worden vermeden?

Virtual Threads zijn ongeschikt voor CPU-bound werklasten of voor situaties die "pinning" veroorzaken. Pinning treedt op wanneer een Virtual Thread aan zijn carrier blijft hangen ondanks blokkering, waardoor het voordeel van virtualisatie verdwijnt.

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();
        }
    }
}

Pinning maakt vanuit het oogpunt van bronnen van een Virtual Thread feitelijk een Platform Thread. De voornaamste oorzaken zijn synchronized-blokken en native JNI-calls. Migreren naar ReentrantLock lost het eerste geval op.

Vraag 6: Hoe spoor je pinning op in een applicatie?

De JVM biedt diagnose-opties om pinning-gevallen te identificeren. Deze informatie is essentieel bij de migratie naar Virtual Threads, omdat pinning de prestaties juist kan verslechteren.

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)

De analyse van pinning-logs onthult de hotspots die moeten worden opgelost. Een applicatie met frequent pinning benut Virtual Threads niet volledig en kan zelfs trager zijn dan met Platform Threads.

Klaar om je Spring Boot gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

Performance-Benchmarks

Vraag 7: Welke performance-winst is met Virtual Threads te verwachten?

Benchmarks tonen aanzienlijke verbeteringen voor typische I/O-bound applicaties. De winst hangt af van de verhouding tussen I/O-tijd en CPU-tijd en van het vereiste concurrency-niveau.

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 verlagen ook het geheugenverbruik, omdat elke thread slechts enkele KB inneemt in plaats van de circa 1MB van een Platform Thread.

Vraag 8: Hoe configureer je connection pools met Virtual Threads?

Connection pools (HikariCP, Lettuce) worden met Virtual Threads de nieuwe bottleneck. Een pool van 10 verbindingen beperkt het aantal gelijktijdige DB-queries tot 10, ook met miljoenen Virtual Threads.

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!");
        }
    }
}

De poolgrootte hangt af van de databasecapaciteit, niet van het aantal Virtual Threads. Een standaard PostgreSQL ondersteunt circa 100-200 actieve verbindingen.

Vraag 9: Hoe meet je de impact van Virtual Threads met Micrometer?

Micrometer en Spring Boot Actuator leveren essentiële metrieken om de effectiviteit van Virtual Threads te beoordelen. Deze metrieken bevestigen de winst en signaleren mogelijke problemen.

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}

De analyse van de metrieken toont de verhouding Virtual Threads / Carriers en identificeert perioden met contentie op de connection pool.

Migratie en Compatibiliteit

Vraag 10: Welke bibliotheken zijn compatibel met Virtual Threads?

De compatibiliteit hangt af van het gebruik van synchronized-blokken en native calls. Het Spring-ecosysteem is grotendeels compatibel, maar enkele bibliotheken vereisen specifieke versies.

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");
            }
        };
    }
}

De meeste moderne frameworks zijn al overgestapt op ReentrantLock. Voor legacy-afhankelijkheden onthult een belastingstest met -Djdk.tracePinnedThreads=short pinning-problemen.

Vraag 11: Hoe migreer je geleidelijk naar Virtual Threads?

Een geleidelijke migratie maakt het mogelijk om de winst te valideren en problemen op te sporen zonder de productie in gevaar te brengen. De aanbevolen strategie isoleert endpoints met Virtual Threads van traditionele endpoints.

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
Let op

Activeer Virtual Threads niet globaal voordat alle afhankelijkheden zijn getest. Pinning kan de prestaties aanzienlijk verslechteren.

Vraag 12: Welke tests moeten vóór een productie-uitrol worden uitgevoerd?

Validatie van Virtual Threads vraagt om belastingstests, pinning-tests en compatibiliteitstests. Deze tests moeten realistische productieomstandigheden simuleren.

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);
    }
}

De tests moeten contentie-scenario's (verzadigde connection pool), timeouts en aanhoudende belasting van enkele minuten omvatten.

Geavanceerde Vragen

Vraag 13: Hoe werken Virtual Threads samen met Structured Concurrency?

Structured Concurrency (JEP 453) vult Virtual Threads aan door te garanderen dat gelijktijdige taken dezelfde levenscyclus delen. Deze aanpak vereenvoudigt foutafhandeling en annulering.

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 voorkomt thread-leaks en vereenvoudigt het debuggen, omdat de call stack de logische codestructuur weerspiegelt.

Vraag 14: Wat is het verschil tussen Virtual Threads en reactieve programmering?

Beide aanpakken lossen hetzelfde probleem op (I/O-efficiëntie), maar met verschillende programmeermodellen. Virtual Threads laten klassieke imperatieve code toe, terwijl reactieve programmering een herschrijving in de vorm van streams vereist.

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()
            ));
    }
}

| Criterium | Virtual Threads | Reactief | |-----------|-----------------|----------| | Leercurve | Laag | Hoog | | Debugging | Klassieke stack traces | Complex | | Backpressure | Handmatig | Native | | Ecosysteem | Groeiend | Volwassen | | Legacy-migratie | Eenvoudig | Herschrijven |

Voor nieuwe applicaties en migraties worden Virtual Threads aanbevolen. Reactieve programmering blijft relevant in situaties die om geavanceerde backpressure vragen.

Vraag 15: Hoe ga je om met ThreadLocal bij Virtual Threads?

ThreadLocal werkt met Virtual Threads, maar verbruikt geheugen per instantie. Scoped Values (JEP 446) bieden een efficiënter alternatief voor het delen van 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);
    }
}

Voor nieuwe ontwikkelingen worden ScopedValues aanbevolen. Voor legacy-code met ThreadLocal is een geleidelijke migratie aan te raden.

Klaar om je Spring Boot gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

Conclusie

Virtual Threads veranderen de Java backend-ontwikkeling door eenvoudige en performante code mogelijk te maken. Belangrijkste aandachtspunten:

Fundamenten:

  • Activatie via spring.threads.virtual.enabled=true
  • Ideaal voor I/O-bound werklasten (REST, DB, bestanden)
  • Vermijden voor CPU-intensieve berekeningen

Performance:

  • Connection pools correct dimensioneren (nieuwe bottleneck)
  • Pinning monitoren met -Djdk.tracePinnedThreads
  • synchronized migreren naar ReentrantLock

Migratie:

  • Stapsgewijs testen per groep endpoints
  • Compatibiliteit van afhankelijkheden valideren
  • Structured Concurrency inzetten voor parallelle operaties

Kennis van Virtual Threads onderscheidt kandidaten die de performance-uitdagingen van moderne applicaties begrijpen. Deze concepten zijn inmiddels onmisbaar in technische interviews over Spring Boot.

Begin met oefenen!

Test je kennis met onze gespreksimulatoren en technische tests.

Tags

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

Delen

Gerelateerde artikelen