Using Project Loom for Lightweight Concurrency in Java
Jul 26, 2025 am 06:41 AMProject Loom introduces virtual threads to solve Java’s concurrency limitations by enabling lightweight, scalable threading. 1. Virtual threads are JVM-managed, low-footprint threads that allow millions of concurrent threads with minimal OS resources. 2. They simplify high-concurrency programming by allowing one thread per request without complex async code. 3. Blocking operations like I/O suspend virtual threads without blocking OS threads, improving throughput. 4. Existing code can adopt virtual threads by replacing traditional thread pools with Executors.newVirtualThreadPerTaskExecutor(). 5. Virtual threads are ideal for I/O-bound tasks like web servers but not for CPU-intensive work. 6. No major code changes are needed, ensuring backward compatibility. As of Java 21, virtual threads are production-ready, offering a simpler, scalable alternative to reactive models while maintaining familiar blocking-style code.
Java’s traditional threading model, based on one-to-one mapping between Java threads and OS threads, has long been a bottleneck for building highly concurrent applications. Each thread consumes significant memory and OS resources, making it impractical to create millions of threads. Project Loom aims to solve this by introducing lightweight concurrency through virtual threads—a major shift in how Java handles concurrency.

The goal? Make it easy to write simple, scalable, and high-throughput concurrent applications without resorting to complex reactive or async programming models.
What Are Virtual Threads?
Virtual threads are lightweight threads managed by the JVM, not the operating system. Unlike platform threads (the traditional java.lang.Thread
), thousands or even millions of virtual threads can run efficiently on a small number of underlying OS threads.

Key characteristics:
- Created and scheduled by the JVM, not the OS
- Low memory footprint – each virtual thread uses far less stack space
-
No need to pool – unlike platform threads, you don’t need thread pools like
ThreadPoolExecutor
-
Drop-in replacement – they still implement
java.lang.Thread
, so most existing code works unchanged
Example:

Thread virtualThread = Thread.ofVirtual() .name("vt-") .unstarted(() -> { System.out.println("Running on virtual thread: " Thread.currentThread()); }); virtualThread.start(); virtualThread.join();
This looks like regular thread code—but it’s now lightweight and scalable.
Why Virtual Threads Improve Concurrency
Traditional server applications often use a thread-per-request model. Under high load, this leads to:
- Thread exhaustion
- High context-switching overhead
- Complex async code (e.g., CompletableFuture, reactive streams)
Virtual threads eliminate these issues by:
- Allowing massive concurrency – spawn one thread per request safely
- Simplifying code structure – keep using blocking I/O and straightforward logic
- Improving throughput – especially for I/O-bound tasks (e.g., HTTP calls, DB queries)
For example, with virtual threads, a web server can handle 10,000 concurrent requests with only a few dozen OS threads—each request runs on its own virtual thread.
How Project Loom Works Under the Hood
Project Loom introduces three key components:
Virtual Threads
Lightweight threads that run on top of a carrier thread (an OS/platform thread).Carrier Threads
The actual OS threads that execute virtual threads. Managed by a ForkJoinPool by default.Continuations (experimental)
A lower-level construct that allows pausing and resuming code execution. Not yet public API, but powers future async patterns.
When a virtual thread blocks (e.g., on I/O), Loom suspends it instead of blocking the carrier thread. The carrier is then free to run another virtual thread. Once the I/O completes, the virtual thread is resumed on any available carrier.
This is transparent to developers—your Thread.sleep()
or socket.read()
just works, but without tying up OS threads.
Practical Example: High-Concurrency HTTP Server
Using virtual threads with Java’s built-in ExecutorService
and HttpServer
:
try (var server = HttpServer.create(new InetSocketAddress(8080), 0)) { server.createContext("/task", exchange -> { try { // Simulate blocking work (e.g., DB call) Thread.sleep(1000); String response = "Hello from " Thread.currentThread(); exchange.sendResponseHeaders(200, response.length()); exchange.getResponseBody().write(response.getBytes()); } finally { exchange.close(); } }); // Use virtual threads for handling requests var executor = Executors.newVirtualThreadPerTaskExecutor(); server.setExecutor(executor); server.start(); System.out.println("Server running on port 8080"); Thread.sleep(60_000); // Run for 1 minute }
Now, even with 10,000 concurrent clients making requests, the server stays responsive with minimal OS threads.
When to Use Virtual Threads
? Best for:
- I/O-bound tasks (web servers, microservices, DB access)
- Applications with high concurrency and low CPU usage
- Simplifying legacy blocking code
? Not ideal for:
- CPU-intensive work (they don’t improve raw compute performance)
- Replacing thread pools for parallel computation (stick with
ForkJoinPool
orparallelStream
)
Also, avoid excessive synchronization (e.g., synchronized
blocks), as they can block carrier threads and reduce scalability.
Migrating Existing Code
One of the biggest wins of Project Loom is backward compatibility. You don’t need to rewrite your code.
To adopt virtual threads:
- Replace
Executors.newFixedThreadPool()
withExecutors.newVirtualThreadPerTaskExecutor()
- Avoid pooling virtual threads (they’re cheap to create)
- Use structured concurrency (new in Java 19 ) for better lifecycle management
Example migration:
// Old: limited pool of platform threads // ExecutorService executor = Executors.newFixedThreadPool(10); // New: unbounded virtual threads ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
Now every task runs on a virtual thread—simple and scalable.
Final Thoughts
Project Loom doesn’t replace reactive programming—but it offers a compelling alternative: write simple, readable, blocking-style code that scales like async.
With virtual threads, Java moves toward a future where high concurrency is no longer synonymous with complexity. Whether you're building microservices, batch processors, or APIs, Loom makes it easier to write efficient, maintainable code.
And the best part? You can start using it today—virtual threads are production-ready as of Java 21.
Basically: if you’re dealing with I/O and concurrency, give virtual threads a try. The performance and simplicity gains are real.
The above is the detailed content of Using Project Loom for Lightweight Concurrency in Java. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undress AI Tool
Undress images for free

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

The settings.json file is located in the user-level or workspace-level path and is used to customize VSCode settings. 1. User-level path: Windows is C:\Users\\AppData\Roaming\Code\User\settings.json, macOS is /Users//Library/ApplicationSupport/Code/User/settings.json, Linux is /home//.config/Code/User/settings.json; 2. Workspace-level path: .vscode/settings in the project root directory

To correctly handle JDBC transactions, you must first turn off the automatic commit mode, then perform multiple operations, and finally commit or rollback according to the results; 1. Call conn.setAutoCommit(false) to start the transaction; 2. Execute multiple SQL operations, such as INSERT and UPDATE; 3. Call conn.commit() if all operations are successful, and call conn.rollback() if an exception occurs to ensure data consistency; at the same time, try-with-resources should be used to manage resources, properly handle exceptions and close connections to avoid connection leakage; in addition, it is recommended to use connection pools and set save points to achieve partial rollback, and keep transactions as short as possible to improve performance.

DependencyInjection(DI)isadesignpatternwhereobjectsreceivedependenciesexternally,promotingloosecouplingandeasiertestingthroughconstructor,setter,orfieldinjection.2.SpringFrameworkusesannotationslike@Component,@Service,and@AutowiredwithJava-basedconfi

itertools.combinations is used to generate all non-repetitive combinations (order irrelevant) that selects a specified number of elements from the iterable object. Its usage includes: 1. Select 2 element combinations from the list, such as ('A','B'), ('A','C'), etc., to avoid repeated order; 2. Take 3 character combinations of strings, such as "abc" and "abd", which are suitable for subsequence generation; 3. Find the combinations where the sum of two numbers is equal to the target value, such as 1 5=6, simplify the double loop logic; the difference between combinations and arrangement lies in whether the order is important, combinations regard AB and BA as the same, while permutations are regarded as different;

TheJVMenablesJava’s"writeonce,runanywhere"capabilitybyexecutingbytecodethroughfourmaincomponents:1.TheClassLoaderSubsystemloads,links,andinitializes.classfilesusingbootstrap,extension,andapplicationclassloaders,ensuringsecureandlazyclassloa

Use classes in the java.time package to replace the old Date and Calendar classes; 2. Get the current date and time through LocalDate, LocalDateTime and LocalTime; 3. Create a specific date and time using the of() method; 4. Use the plus/minus method to immutably increase and decrease the time; 5. Use ZonedDateTime and ZoneId to process the time zone; 6. Format and parse date strings through DateTimeFormatter; 7. Use Instant to be compatible with the old date types when necessary; date processing in modern Java should give priority to using java.timeAPI, which provides clear, immutable and linear

ChromecanopenlocalfileslikeHTMLandPDFsbyusing"Openfile"ordraggingthemintothebrowser;ensuretheaddressstartswithfile:///;2.SecurityrestrictionsblockAJAX,localStorage,andcross-folderaccessonfile://;usealocalserverlikepython-mhttp.server8000tor

Laravel's error and exception handling mechanism is based on the PHP exception system and Symfony component, and is managed by the App\Exceptions\Handler class. 1. Record exceptions through the report() method, such as integrating Sentry and other monitoring services; 2. Convert exceptions into HTTP responses through the render() method, supporting custom JSON or page jumps; 3. You can create custom exception classes such as PaymentFailedException and define their response format; 4. Automatically handle verification exception ValidationException, and manually adjust the error response structure; 5. Decide whether to display details based on the APP_DEBUG configuration.
