Part 2. Introducing virtual threads

Part 2. Introducing virtual threads

This is an extension of this post: {https://shorturl.at/DeIqm}


How do virtual threads work??

A virtual thread runs on a carrier thread (platform), and you cannot avoid that because the platform thread is the only way you have to run things in parallel on the OS.?

At the core of it, there is a special thread pool, fork join pool, this is the second fork join pool on the jvm, the first one being the common fork join pool used to run parallel streams.?

The virtual thread is mounted on a platform thread from the fork-join pull; it has its stack trace and everything, but it's still running on top of a platform thread.?

However, the API uses a unique logic called continuation, which uses the hidden object in the JVM. This logic moves the stack trace of the virtual thread from the platform thread it is mounted on to the heap as soon as the virtual thread is blocked. Now, this platform thread is available to run other virtual threads. The continuation registers a signal that triggers when the blocking operation is done, and at this point, the virtual thread is returned to a free platform thread.

This makes writing blocking code in virtual threads okay and desirable.?

So, compared to reactive programming, it is much easier because we don't have to worry about blocking. Blocking the virtual thread will never block a platform thread, while in reactive programming, we have to make sure never to block, as it might block the platform thread (which might be serving multiple requests, so we're essentially blocking numerous requests).

This is what code with virtual threads looks like:?

?Callable<Images> fetchImages = () -> someService.fetchImages(); 
var f = Executors.newVirtualThreadPerTaskExecutor().submit(fetchImages); 
System.out.println("f = " + f.get());         

If something goes wrong:?

We see where the exception happened and who was the caller.?

In previous examples, we had two requests we wanted to run in parallel. We can do this by creating a new virtual thread and then calling the get at the end for both tasks, but there is an even better approach.?

try (var scope = new StructuredTaskScope<>()) { 
  var imagesSubtask = scope.fork(() -> someService.readImages()); 
  var linksSubtask = scope.fork(() -> someService.readImages()); 
 
  scope.join(); 
 
  var page = new Page(imagesSubtask.get(), linksSubtask.get()); 
} catch (InterruptedException e) { 
  // Handle exception 
}        

This is using the new structured concurrency API.?

If something goes wrong in readimages:

?

We can check the task's state and get the exception stack trace.?And this is the exception that you get:

This tells you exactly where the service was called and where it failed.?

So this fixes all the problems we mentioned before:?

  • Blocking platform thread is bad?

Blocking a virtual thread is ok; it never blocks the platform thread?

  • Having a non-relevant stack trace is annoying?

Fixed, your stack trace tells you exactly where the call happened?

  • Having loose threads is hard to fix?

There will be no more loose threads because, with AutoCloseable, all resources will be cleaned up using structured concurrency with try-with-resources.

Virtual threads?

  • Are as efficient as reactive models?

  • Provide a simple programming model (good old imperative model)?

  • Make debugging possible and easy?

  • Make profiling possible?

ThreadLocal and ScopedValue?

ThreadLocal? These variables differ from their regular counterparts in that each thread that accesses one (via its get or set method) has its own independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).?

What is wrong with ThreadLocal?

  • they are mutable?

  • the VM cannot optimize them?

  • they may be kept alive forever?

Virtual threads support ThreadLocal variables.?ScopedValue is a model that is supposed to replace ThreadLocal.

  • they are not mutable?

  • they are not bound to a particular thread?

  • they are bound to a single method call?

Consider the following example with a scoped value USERNAME that is bound to the value "duke" for the execution, by a thread, of a run method that invokes doSomething().?

private static final ScopedValue<String> USERNAME =  ScopedValue.newInstance(); 
 
ScopedValue.where(USERNAME, "duke", () -> doSomething());         

Inheritance?

ScopedValue supports sharing data across threads. However, this sharing is limited to structured cases where child threads are started and terminated within the bounded period of execution by a parent thread. More specifically, when using a?StructuredTaskScope, scoped value bindings are?captured?when a StructuredTaskScope is created and inherited by all threads started in that scope with the?fork?method.?

In the following example, the ScopedValue USERNAME is bound to the value "duke" for executing a runnable operation. The code in the run method creates a StructuredTaskScope and forks three child threads. Code executed directly or indirectly by these threads running childTask1(), childTask2(), and childTask3() will read the value "duke."?

private static final ScopedValue<String> USERNAME = ScopedValue.newInstance(); 
 
    ScopedValue.where(USERNAME, "duke", () -> { 
        try (var scope = new StructuredTaskScope<String>()) { 
 
            scope.fork(() -> childTask1()); 
            scope.fork(() -> childTask2()); 
            scope.fork(() -> childTask3()); 
 
            ... 
         } 
    });         

In a nutshell?

Virtual threads and structured concurrency fix the problems of reactive/asynchronous programming without giving up performance.?

ScopedValue fixes the problems of ThreadLocal variables.?

This post builds on the previous discussion. Check it out here ?? https://shorturl.at/DeIqm

回复

要查看或添加评论,请登录

Softray Solutions的更多文章