Mastering Asynchronous Batch Processing with JDK 21 Virtual Threads
Using JDK 21’s standardized Virtual Threads, this guide explains how to design and implement robust asynchronous batch processing, covering common pitfalls like CPU spikes and OOM, best‑practice concurrency controls, task queue architecture, and practical code illustrations.
Introduction
During business system development, large‑scale data processing can cause CPU spikes, OOM, or overload external services.
CPU spikes leading to system lag or crash.
OOM causing endless restarts.
Excessive requests breaking databases or external systems.
This article shows how to use the newly standardized Virtual Threads in JDK 21 to implement asynchronous batch processing.
Best Practices for Using JDK 21 Virtual Threads
Create virtual threads synchronously; they are cheap and not scarce.
One virtual thread per concurrent task; no pooling needed.
Control concurrency with a Semaphore.
Avoid ThreadLocal as it may consume memory and is not thread‑safe.
Prevent carrier thread suspension; avoid synchronized or Object.wait() inside virtual thread code.
Asynchronous Batch Processing Design
Identify business operations that can be executed asynchronously (e.g., SMS, email, verification codes, internal messages, bulk invoice callbacks).
Define a task message containing task ID, type (enum), and JSON payload; bind each type to a handler and a semaphore.
Persist the task message to a queue (e.g., JDK BlockingQueue, Redisson, RocketMQ). Optionally use a delay queue to schedule future execution.
Run a carrier thread with an infinite while loop that polls the queue; block when the queue is empty.
Before launching a virtual thread, acquire a semaphore permit for the task type; block the carrier thread if permits are unavailable.
Each task type maps to a separate queue topic and its own carrier thread, preventing semaphore contention across types.
Start a virtual thread to execute the business logic defined by the task‑type enum; release the semaphore in a finally block.
Classify task types (CPU‑bound, I/O‑bound, resource‑limited) and tune semaphore limits accordingly to avoid CPU 100 %, OOM, or external system crashes.
If a task throws an exception, keep the carrier loop running, log the error, and design an automatic retry strategy.
Appendix
Code Structure
Task Type Design
Semaphore Configuration
Testing
The test uses JDK's LinkedBlockingQueue as the in‑memory queue. Because a multi‑topic mode is not implemented, semaphore permits for different task types may compete.
Concurrent Result Retrieval
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Eric Tech Circle
Backend team lead & architect with 10+ years experience, full‑stack engineer, sharing insights and solo development practice.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
