5

Guide of Virtual Threads – Lightweight threads in Java

 1 year ago
source link: https://blog.adamgamboa.dev/guide-of-virtual-threads-lightweight-threads-in-java/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Guide of Virtual Threads – Lightweight threads in Java

Advertisements

Since the very beginning – back to 1997 – Java provided an easy way to work with multi-thread applications. Yes, we are talking about the Thread class and the Runnable interface. With new Java versions, the core included more a more ways simplify working with concurrency in Java. For example: The Executor Service, ForkJoinPool, Future, CompletableFuture (which are very similar to promises in Java Script), the parallel stream, the java.concurrent.* package with collections, utilities, locks and more.

All those features, makes Java a rich ecosystem to work with multi-thread application, however they have been limited to the OS threads. In big applications with hundreds of concurrent process might not be efficient enough, and might not scale easily, requiring to add more cpu to provide more available threads. This is primarily due to the shared state concurrency model used by default.

That’s why Project Loom started in 2017, and an initiative to provide lightweight threads that are not tied to OS threads but are managed by the JVM. A lot of changes and proposals from the first draft, but it seems to be ready in Java 21.

Project Loom and Virtual Threads

As mentioned before, project Loom started as an internal initiative to provide lightweight threads in Java, passing through different drafts and proposals about how to do it, concepts like Fiber and Channels renamed to the actual concept of Virtual Threads. Project loom was incubating some of those features in previous versions of Java – added in Java 19 in preview mode available to developers to provide feedback-, the preview stage finished and now the features are available as part of Java 21.

Virtual Threads are a new kind of threads added to Java, which are not tied to the OS threads like the “Platform Threads”. They are lightweight because it is possible to have thousands and even millions of Virtual Threads with minimum of resources, as they are managed by the JVM, the limit is the memory in the JVM.

If you have worked with Kotlin or Go Lang, you will notice that Virtual Threads are in deed very similar to Go routines or Kotlin coroutines. Some developers move to those languages because of those features, it seems Java is now providing the same in a backward compatible approach – For the Java Lovers, this is one more reason to continue using Java -.

Thread.startVirtualThread(() -> {
    System.out.println("Hello, Virtual Thread!");
});
Thread.startVirtualThread(() -> {
    System.out.println("Hello, Virtual Thread!");
});
go func() {
    println("Hello, Goroutines!")
}()
go func() {
    println("Hello, Goroutines!")
}()
runBlocking {
    launch {
        println("Hello, Kotlin coroutines!")
    }
}
Kotlin
runBlocking {
    launch {
        println("Hello, Kotlin coroutines!")
    }
}

How to use virtual threads

The Thread class provides some new builder methods that can be used to create a VirtualThread. For example: startVirtualThread, which creates and starts a Virtual thread in one single operation.

var task = () -> System.out.println("Hello World!");
// Creating and starting the thread in the same operation
Thread.startVirtualThread(task);
var task = () -> System.out.println("Hello World!");
// Creating and starting the thread in the same operation
Thread.startVirtualThread(task);

By using the ofVirtual() which returns a Builder instance to create step by step the desired thread.

Advertisements
//Creating the Virtual thread, and then Running the start method. 
var thread = Thread.ofVirtual().start(task);
var thread2 = Thread.ofVirtual().name("My Thread").start(task); 

//If need to create the thread but not start it yet
var unstartedThread = Thread.ofVirtual().unstarted();
//... some other logic... 
unstartedThread.started(); //Now starts the thread
//Creating the Virtual thread, and then Running the start method. 
var thread = Thread.ofVirtual().start(task);
var thread2 = Thread.ofVirtual().name("My Thread").start(task); 
//If need to create the thread but not start it yet
var unstartedThread = Thread.ofVirtual().unstarted();
//... some other logic... 
unstartedThread.started(); //Now starts the thread

In addition to that, the Builder can return a factory instance from the can we can build VirtualThreads on demand.

// Virtual thread created with a factory
ThreadFactory factory = Thread.ofVirtual().factory();
Thread virtualThreadFromAFactory = factory.newThread(task);
virtualThreadFromAFactory.start();
// Virtual thread created with a factory
ThreadFactory factory = Thread.ofVirtual().factory();
Thread virtualThreadFromAFactory = factory.newThread(task);
virtualThreadFromAFactory.start();

Executor Service and Virtual Threads

Thread Pools are a valid strategy to reuse the expensive Platform Threads for multiple tasks, but with Virtual Threads -which are lightweight and cheap – creating a pool of them is not necessary. The ExecutorService is an API providing some thread pools in Java, and extensively used in many applications. Therefore, wasting resources in pooling very cheap objects usually with a short life time does not make sense, that’s why it’s not recommend to pool Virtual Threads.

If you want to take advantage of the benefits of Virtual Threads but without refactoring your existing code using the ExecutorService , then you can use the implementation VirtualThreadPerTaskExecutor . As it name indicates, it creates a Executor which creates VirtualThread per Task, in other words it’s not reusing or “pooling” the virtual threads.

Runnable task = () -> System.out.println("Hello, world");
ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();
executorService.execute(task);
Runnable task = () -> System.out.println("Hello, world");
ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();
executorService.execute(task);

Or by adding a ThreadFactory instance which returns VirtualThreads to any other Executors methods.

ThreadFactory factory = Thread.ofVirtual().factory();
Executors.newThreadPerTaskExecutor(factory); // Same as newVirtualThreadPerTaskExecutor
Executors.newSingleThreadExecutor(factory);
Executors.newCachedThreadPool(factory);
Executors.newFixedThreadPool(1, factory);
Executors.newScheduledThreadPool(1, factory);
Executors.newSingleThreadScheduledExecutor(factory);
ThreadFactory factory = Thread.ofVirtual().factory();
Executors.newThreadPerTaskExecutor(factory); // Same as newVirtualThreadPerTaskExecutor
Executors.newSingleThreadExecutor(factory);
Executors.newCachedThreadPool(factory);
Executors.newFixedThreadPool(1, factory);
Executors.newScheduledThreadPool(1, factory);
Executors.newSingleThreadScheduledExecutor(factory);

That’s the recommend first step to start using Virtual Threads if using the Executor Service. As you can see minimal changes to the existing code, and backward-compatible. Then, a more deep refactor in the code can be done if needed.

Considerations

  • A virtual thread can be scheduled on different carriers over the course of its lifetime; in other words, the scheduler does not maintain affinity between a virtual thread and any particular platform thread. It means a virtual thread which is running over the Platform Thread A, and sleep for some time, when it continues, it might run Platform Thread B, or C, D.
  • Virtual threads are always daemon threads. The Thread.setDaemon(boolean) method cannot change a virtual thread to be a non-daemon thread.
  • Virtual threads have a fixed priority of Thread.NORM_PRIORITY. The Thread.setPriority(int) method has no effect on virtual threads.
  • Virtual threads have no permissions when running with a SecurityManager set.

Conclusion

Even though Virtual Threads are compatible to “Platform Threads” developer are not going to start refactoring their code to use it – If it’s working, don’t touch it – but new developments can take advantage of them. Also, third party libraries for reactive or asynchronous programming can start using them under the hood to provide a more scalable solution.

And I hope to see how lightweight Java web-servers (like tomcat, netty, jetty, and others) can start using Virtual Threads to provide an scalable solution to provide thousands of concurrent request. For us, the developers using those web-servers, there should be not change in the code, just take advantage of the new performance enhancements on them.

References

Post Views: 41
Advertisements

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK