- 3.1 Motivation for Virtual Threads
- 3.2 Virtual Thread Execution Model
- 3.3 Using Thread Class to Create Virtual Threads
- 3.4 Using Thread Builders to Create Virtual Threads
- 3.5 Using Thread Factory to Create Threads
- 3.6 Using Thread Executor Services
- 3.7 Scalability of Throughput with Virtual Threads
- 3.8 Best Practices for Using Virtual Threads
- Review Questions
3.3 Using Thread Class to Create Virtual Threads
The Thread class supports virtual threads and provides new methods for this purpose. We cannot use a constructor of the Thread class to create a virtual thread as all constructors create platform threads. However, the Concurrency API provides great flexibility in creating and running virtual threads, as we will see in the rest of this chapter.
Logging Information during Program Execution
Using blocking operations (such as print methods of the System.out stream) to write information about program execution in concurrent applications can adversely affect program performance. The Logging API provides a simple, yet flexible non-blocking mechanism to log such information. There are no direct questions on the exam in this topic, but use of loggers may be encountered in the context of other questions, such as in the context of the Concurrency API or execution of parallel streams.
Steps (1)-(4) shown in Example 3.1 are sufficient to create and use a logger for our purpose, which we will be doing in some examples in this chapter:
Import the Logger class.
Declare a static field and create an instance of the Logger class.
Provide a static initializer block to set property for appropriate format to be used by the formatter when logging messages. For example: [Timestamp] INFO: ...
Call the info() method of the logger in the program to log messages.
Example 3.1 Using a Logger
package vt;
import java.util.logging.Logger; // (1)
public class Main {
private static final Logger logger = // (2)
Logger.getLogger(Main.class.getName());
static { // (3)
System.setProperty("java.util.logging.SimpleFormatter.format",
"[%1$tT.%1$tN] %4$s: %5$s%n");
}
public static void main(String[] args) {
logger.info("Log this info."); // (4)
}
}
Probable output from the program:
[12:22:11.357397000] INFO: Log this info.
Creating and Starting a Virtual Thread
The simplest way to create and start a virtual thread is to use the static method startVirtualThread() of the Thread class, as shown at (2) in Example 3.2, passing the task that is to be executed.
Thread vt = Thread.startVirtualThread(task); // (2)
A task is defined at (1) as a Runnable that prints the string representation of the current thread, which in this case will be a virtual thread, when the task is executed. Note that the print method is a blocking operation.
Runnable task = () -> System.out.println(Thread.currentThread()); // (1)
The virtual method created and started at (2) will execute the task at (1) when it is allowed to run, printing information about the virtual thread:
VirtualThread[#21]/runnable@ForkJoinPool-1-worker-1
In this case, the string representation of the current thread comprises of the following components:
VirtualThread identifies that it is a virtual thread.
#21 specifies the unique thread ID of the virtual thread. In this case it is 21. The thread ID does not change during the lifetime of a thread.
runnable indicates the state the thread is in. In this case, it is in the runnable state.
ForkJoinPool-1-worker-1 is composed of the name of the fork-join pool and the name of the carrier thread on which the virtual thread is mounted. The name ForkJoinPool-1 identifies the fork-join pool that manages the carrier threads (which are platform threads). Designation worker-1 is the name of the carrier thread in the fork-join pool ForkJoinPool-1 on which the virtual thread with ID #21 is mounted.
The number value in the thread ID designation, the carrier thread designation, and the fork-join pool designation are counter values giving an indication of how many of these entities have been created.
Virtual threads are daemon threads—that is, they are unceremoniously terminated when the parent platform thread terminates. The virtual thread created at (2) in Example 3.2 can risk being terminated before it has completed if the parent platform thread (in this case, the main thread) completes first. In order to allow the virtual thread to complete its execution, the parent thread can call the join() method on the virtual thread in order to wait for its completion before proceeding. The call at (4) will ensure that the main thread will wait indefinitely and does not proceed before the virtual thread completes its execution.
vt.join(); // (4)
In Example 3.2, the main thread logs information about whether the virtual thread is alive before and after calling the join() method on the virtual thread. The logged information shows that virtual thread #21 was alive before the call to the join() method, but had completed its execution after the call.
Finally, the information logged at (5) in Example 3.2 shows that a virtual thread is an instance of the java.lang.VirtualThread class. This class is a non-public subclass of the Thread class in the java.lang package and not accessible outside this package. For all intents and purposes, it is the Thread class that provides the support for virtual threads. However, the application has to keep track of whether a reference of type Thread denotes a virtual or a platform thread.
Example 3.2 Creating and Running a Virtual Thread
package vt;
import java.util.logging.Logger;
public class BasicVTCreation {
private static final Logger logger =
Logger.getLogger(BasicVTCreation.class.getName());
static {
System.setProperty("java.util.logging.SimpleFormatter.format",
"[%1$tT.%1$tN] %4$s: %5$s%n");
}
public static void main(String[] args) throws InterruptedException {
// Create a task:
Runnable task = () -> System.out.println(Thread.currentThread()); // (1)
// Create and start a virtual thread that is assigned a task:
Thread vt = Thread.startVirtualThread(task); // (2)
logger.info("Before join, vt #" + vt.threadId() + // (3)
" is " + (vt.isAlive() ? "alive." : "not alive."));
vt.join(); // (4)
logger.info("After join, vt #" + vt.threadId() + // (5)
" is " + (vt.isAlive() ? "alive." : "not alive."));
// Class of a virtual thread:
logger.info("A virtual thread is an instance of " + vt.getClass()); // (6)
}
}
Probable output from the program:
VirtualThread[#21]/runnable@ForkJoinPool-1-worker-1 [17:43:54.118896000] INFO: Before join, vt #21 is alive. [17:43:54.185280000] INFO: After join, vt #21 is not alive. [17:43:54.186162000] INFO: A virtual thread is an instance of class java.lang.Vir- tualThread
The following is a summary of selected new methods in the java.lang.Thread class since Java 17:
