
A lightweight computing thread or process is basically a minimal set of tasks that can be executed by the operating system and run within the context of a process. Depending on the number of CPU cores, multiple tasks can be executed simultaneously (parallelism). If there are more threads than CPU cores, scheduling will be required, whereby threads take up a CPU for a specific amount of time and then release it so another thread can take it, giving the impression of parallelism.
The Java Virtual Machine (JVM) itself is thread-based and has a 1-to-1 equivalence with the threads of the underlying operating system.
Now, in a Web system, each User request is handled by a thread created for that purpose ( Thread per request model), so... What happens when 1000 users make a call to the aforementioned Web system?

Thread per request model
As the name suggests, each incoming request is served by a thread. When the maximum number of simultaneously running threads is reached, the remaining requests must be queued, waiting for an available thread.
When programming there are basically two styles, Synchronous and Asynchronous.

In synchronous programming, a thread makes a request to an external service (database, another service, etc.) and waits for the response without doing anything. In contrast, in asynchronous programming, when such a request is made, the thread is not blocked and can continue doing things while receiving the response.
Synchronous programming is simpler because responses are received in the context in which the request was made. On the other hand, it is less efficient, as there are long wait times for calls to be resolved.

In the asynchronous case, it's quite the opposite; it's more complex because you have to program knowing that the response may arrive in a different execution context than the one in which the request was made, which complicates programming. However, since the threads never stop, it's more efficient.
Virtual Threads
Virtual threads are lightweight threads designed to reduce the effort of writing and maintaining high-performance concurrent applications. For more information, see JEP 444.
Virtual threads are NOT managed by the operating system, but by the JVM itself. We saw earlier that each JVM thread has a corresponding OS thread (Platform Thread).
The idea now is that these JVM threads called carrier threads are assigned to virtual threads when it is time for them to execute, thereby achieving a multitude of virtual threads with few carrier threads and, therefore, few operating system threads, thus gaining in efficiency and better resource management.
Example executions
The scenario presented is a simple invocation of a REST service that returns a string. The base framework used is Spring Boot 3.2.3 and the default web server is Tomcat. To execute traditional threads, the server configuration will not be modified, but to execute virtual threads, Tomcat will be forced to use an Executor that uses virtual threads to handle requests ( Executors.newVirtualThreadPerTaskExecutor).
Executions with traditional threads
Statistics with 100 concurrent users :

Resources with 100 concurrent users :

Statistics with 1000 concurrent users :

Resources used with 1000 concurrent users :

Execution with virtual threads
Statistics with 100 concurrent users :

Resources with 100 concurrent users :

Statistics with 1000 concurrent users :

Resources with 1000 concurrent users:

Conclusions
As can be seen, the use of virtual threads not only reduces resource consumption, but also keeps resource consumption constant as workloads increase.
Note that the number of threads ( carrier threads ) created in the virtual threads scenario has remained more or less constant (28-30) even though the load has increased by an order of magnitude ( from 100 to 1000 users).
In the case of traditional threads, this has not been the case, with an increase of almost 100 threads (125 to 222) when the load has increased in the same terms as in the virtual thread scenario.
As for statistics, it can be seen that both at low load (100 users) and high load (1000), the behavior of the virtual threads is much better.
Have you already heard about Hunters?
Being a Hunter means accepting the challenge of testing new solutions that deliver differentiated results. Join the Hunters program and become part of a cross-functional group capable of generating and transferring knowledge.
Get ahead of the digital solutions that will drive our growth. Find more information about Hunters on the website.