Virtual Threads/Project Loom/Fiber

Virtual Threads即协程,将在Java 21 GA,针对这两年一直追的内容,做一些总结。

在Java中与Virtual Threads 相关的还有Structured Concurrency 和 Scoped Values,前者简化了对VT多线程的管理,而后者可以视为VT下的ThreadLocal。下面是摘录的各种文档。

// 对VT,最简单的使用方式就是直接使用
Thread.ofVirtual().start();
// 为了兼容先前的版本,也可装模作样的池化一下
var executorService = Executors.newVirtualThreadPerTaskExecutor();
// 也可基于factory来自定义name
var virtualFactory = Thread.ofVirtual().name("Virtual-Async").factory();
var executorService = Executors.newThreadPerTaskExecutor(virtualFactory);

为了管理多线程,可以使用Structured Concurrency

    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        Supplier<String>  one  = scope.fork(() -> findOne());
        Supplier<Integer> two = scope.fork(() -> fetchTwo());

        scope.join()            // Join both subtasks
             .throwIfFailed();  // ... and propagate errors

        // Here, both subtasks have succeeded, so compose their results
        return new Response(one.get(), two.get());
    }

针对需要线程内共享的需求,可使用ScopedValues

final static ScopedValue<...> V = ScopedValue.newInstance();

// In some method
ScopedValue.where(V, <value>)
           .run(() -> { ... V.get() ... call methods ... });

// In a method called directly or indirectly from the lambda expression
... V.get() ...

在Spring Boot 3.2之前,可以这样配置 Web Servers & Task Execution使用VT

// 需在配置文件中配置
//spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
    @Bean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
    public AsyncTaskExecutor asyncTaskExecutor() {
        var virtualFactory = Thread.ofVirtual().name("Virtual-Async").factory();
        return new TaskExecutorAdapter(Executors.newThreadPerTaskExecutor(virtualFactory));
    }

    @Bean
    public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
        var virtualFactory = Thread.ofVirtual().name("Virtual-WebServer").factory();
        return protocolHandler -> protocolHandler.setExecutor(Executors.newThreadPerTaskExecutor(virtualFactory));
//        从Tomcat 10.1.10起可以使用下面这个
//        return protocolHandler -> protocolHandler.setExecutor(new VirtualThreadExecutor("Virtual-WebServer-"));
    }

从Spring Boot 3.2起,只需要一条配置即可,会应用到Servlet Web Servers, Task Execution, Task Scheduling, Blocking Execution with Spring WebFlux, Http-NIO of Reactor, RabbitMQ/Kafka listener

spring:
  threads:
    virtual:
      enabled: true
比较推荐Spring Framework中的这个issue

Compatibility with virtual threads (OpenJDK's Project Loom)
在Spring Boot 3.3,Support Undertow web server,Websockets

  • Project Loom(Preview in Java 19, Java 20, Will GA in Java 21)
  • JEP 425: Virtual Threads
  • JEP 436: Virtual Threads (Second Preview)
  • JEP 444: Virtual Threads She is coming🎉
  • Project Loom
  • Project Loom: Fibers and Continuations for the Java Virtual Machine
  • In general, the fiber API will be nearly identical to that of Thread as the abstraction is the same, and we’d also like to run code that so far has run in kernel threads to run in fibers with little or no modification. This immediately suggests two design options:
    • Represent fibers as a Fiber class, and factor out the common API for Fiber and Thread into a common super-type, provisionally called Strand. Thread-implementation-agnostic code would be programmed against Strand, so that Strand.currentStrand would return a fiber if the code is running in a fiber, and Strand.sleep would suspend the fiber if the code is running in a fiber.
    • Use the same Thread class for both kinds of threads — user-mode and kernel-mode — and choose an implementation as a dynamic property set in a constructor or a setter called prior to invoking start.
    • A separate Fiber class might allow us more flexibility to deviate from Thread, but would also present some challenges. Because a user-mode scheduler does not have direct access to CPU cores, assigning a fiber to a core is done by running it in some worker kernel thread, and so every fiber has an underlying kernel thread, at least while it is scheduled to a CPU core, although the identity of underlying kernel thread is not fixed, and may change if the scheduler decides to schedule the same fiber to a different worker kernel thread. If the scheduler is written in Java — as we want — every fiber even has an underlying Thread instance. If fibers are represented by the Fiber class, the underlying Thread instance would be accessible to code running in a fiber (e.g. with Thread.currentThread or Thread.sleep), which seems inadvisable.
    • If fibers are represented by the same Thread class, a fiber’s underlying kernel thread would be inaccessible to user code, which seems reasonable but has a number of implications. For one, it would require more work in the JVM, which makes heavy use of the Thread class, and would need to be aware of a possible fiber implementation. For another, it would limit our design flexibility. It also creates some circularity when writing schedulers, that need to implement threads (fibers) by assigning them to threads (kernel threads). This means that we would need to expose the fiber’s (represented by Thread) continuation for use by the scheduler.
    • Because fibers are scheduled by Java schedulers, they need not be GC roots, as at any given time a fiber is either runnable, in which case a reference to it is held by its scheduler, or blocked, in which case a reference to it is held by the object on which it is blocked (e.g. a lock or an IO queue), so that it can be unblocked. (Fiber相关的,只有在结束运行后,被GC掉)
    • Loom有两种实现方式,一种是抽取公共父类,与Thread平级。另一种是作为Thread的一部分。每一种都有优缺点。既然最终的实现叫Virtual Thread,且将其相关加入到Thread的API中,显然用的第二种方式(In the current prototype, virtual threads are implemented by the java.lang.Thread API.)
    • The current prototype implements the mount/dismount operations by copying stack frames from the continuation stack – stored on the Java heap as two Java arrays, an Object array for the references on the stack and a primitive array for primitive values and metadata. Copying a frame from the thread stack ( which we also call the vertical stack, or the v-stack) to the continuation stack (also, the horizontal stack, or the h-stack) is called freezing it, while copying a frame from the h-stack to the v-stack is called thawing. The prototype also optionally thaws just a small portion of the h-stack when mounting using an approach called lazy copy; see the JVMLS 2018 talk as well as the section on performance for more detail.(针对线程的Stack,采用的方式是yield时,copy stack, stored on the Java heap)
    • To run code in a virtual thread, the JDK’s virtual thread scheduler assigns the virtual thread for execution on a platform thread by mounting the virtual thread on a platform thread. This makes the platform thread become the carrier of the virtual thread. Later, after running some code, the virtual thread can unmount from its carrier. At that point the platform thread is free so the scheduler can mount a different virtual thread on it, thereby making it a carrier again.
    • Typically, a virtual thread will unmount when it blocks on I/O or some other blocking operation in the JDK. When the blocking operation is ready to complete, it submits the virtual thread back to the scheduler, which will mount the virtual thread on a carrier to resume execution.(简而言之。I/O block不会block Platform Thread, Kotlin、Go的协程实现也是不会阻塞)
    • There are two scenarios in which a virtual thread cannot be unmounted during blocking operations because it is pinned to its carrier:
      • When it executes code inside a synchronized block or method, or
      • When it executes a native method or a foreign function.
      • 以上是两种会block的情况,所以较synchronized 更推荐使用API level的 Lock实现,Spring也因此在3.2中refactor部分实现
    • The scheduler does not compensate for pinning by expanding its parallelism. Instead, avoid frequent and long-lived pinning by revising synchronized blocks or methods that run frequently and guard potentially long I/O operations to use java.util.concurrent.locks.ReentrantLock instead.
    • The primitive API to support locking, java.util.concurrent.LockSupport, now supports virtual threads。
  • Virtual Threads in Spring 6.x
  • Blog-Understanding Java’s Project Loom
  • IntelliJ IDEA Conf 2022 | Project Loom: Revolution in Concurrency or Obscure Implementation Detail?
  • Running Kotlin Coroutines on Project Loom
  • Spring Boot 3.2 ships support for Virtual Threads in 3.2-M1
  • Some synchronized blocks have been refactored to use ReentrantLock instead in 3.2-M2.
  • Spring Framework: General compatibility with virtual threads
  • 通过JProfiler可以查看Thread的情况,这一点不得不说收费的就是不一样。VisualVM和jconsole都看不到类型信息。 可以看的基本的main Thread,及19后听到的CarrierThreads和VirtualThreads img



Leave a Reply

Your email address will not be published. Required fields are marked *

lWoHvYe 无悔,专一