[Java] a taste of the Loom project of Java collaborative programming

Posted by Skara on Sun, 13 Feb 2022 04:03:07 +0100

1. General

First, let's take a look at an interesting morning meeting related to the cooperation process: [java] java collaboration

Reprint: A taste of the Loom project of Java collaborative programming

For a long time, I paid attention to the development progress of JDK collaboration library, but I was busy a while ago and seldom checked the content of OpenJDK official website. The Java collaborative project room (because the project is still in the development stage, the official website given by OpenJDK) https://openjdk.java.net/projects/loom Only a small amount of information related to the Loom project) has been approved before 2018. At present, early versions based on JDK17 compilation and JDK18 compilation have been released. When downloading the early version of Loom, the author only found the version compiled by JDK18:


The download portal is at: https://jdk.java.net/loom

Because the JDK version is too high, you can use the mainstream IDE to import room-jdk-18 + 9 for code highlighting and syntax reminder. For the time being, you can't find a method to compile. For the time being, you can use the javac command script under the JDK execution directory to compile and run it with the java command script.

Brief introduction to Loom project

Loom - Fibers, Continuations and Tail-Calls for the JVM

The title of the room project has highlighted three new features introduced:

  • Fibers: a few years ago, I saw that the test code of the room project at that time used the Fiber API (now the API has been removed), which means lightweight thread, that is, coprocess, also known as lightweight user thread. It is amazing that it is actually called Virtual Thread in the current JDK

  • Continuations: literally translated as "continuity", the implementation is a bit like closure. With reference to many materials, the specific meaning has not been accurately understood. It feels that "rough" can be interpreted as "what will the program execute next" or "the next code block to execute"

  • Tail calls: tail call VM level support

The three new features are not expanded in detail. At present, they are only the EA version, and there is still the possibility of modification, so it is not necessary to expand them in detail.

Virtual Thread usage

In the current version of the Loom project, a new open virtual Thread VirtualThread class is not introduced into the collaboration. Although VirtualThread does exist, this class uses the default modifier and is hidden in Java Lang package, and VirtualThread is a subclass of Thread. The creation API of the coroutine is located in the Thread class:


Create a collaboration using this API as follows:

JAVA
public static void main(String[] args) {
    Thread fiber = Thread.startVirtualThread(() -> System.out.println("Hello Fiber"));
}

From the current source code:

  • VirtualThread will pass through thread Currentthread() gets the scheduler of the parent thread. If it runs in the main method, the parent thread of the collaboration instance in the above code is the main thread

  • The default scheduler is the ForkJoinPool instance (VirtualThread.DEFAULT_SCHEDULER) created by the system. The input Runnable instance will be encapsulated as RunContinuation and finally executed by the scheduler

  • For the timed unpark (blocking and waiting for wake-up), use the ScheduledExecutorService instance created by the system to wake up

  • This static factory method runs as soon as it creates a collaboration, and returns a collaboration instance

If you follow the above Thread The startvirtualthread () method is used to create a collaboration process. Obviously, it is unable to define the name and other attributes of the collaboration process. The Loom project introduces the builder mode for the Thread class, which reasonably solves this problem:

// Create a platform Thread builder corresponding to the Thread instance
public static Builder.OfPlatform ofPlatform() {
    return new ThreadBuilders.PlatformThreadBuilder();
}

// Create a virtual thread builder corresponding to VirtualThread
public static Builder.OfVirtual ofVirtual() {
    return new ThreadBuilders.VirtualThreadBuilder();
}

Simply put:

  • The ofPlatform() method is used to build the thread instance. The Platform Thread here is actually Java lang.Thread

  • The ofVirtual() method is used to build a VirtualThread instance, that is, a collaboration instance
    All Setter method chains of the two builder instances are expanded as follows:

public static void main(String[] args) {
    Thread.Builder.OfPlatform platformThreadBuilder = Thread.ofPlatform()
            // Daemon thread
            .daemon(true)
            // Thread group
            .group(Thread.currentThread().getThreadGroup())
            // Thread name
            .name("thread-1")
            // Thread name prefix + starting self incrementing number = > prefix + start. The next created thread name is prefix + (start + 1)
            // If start > 0, the name attribute configuration will be overwritten
            .name("thread-", 1L)
            // Enable ThreadLocal
            .allowSetThreadLocals(false)
            // Enable InheritableThreadLocal
            .inheritInheritableThreadLocals(false)
            // set priority
            .priority(100)
            // Set thread stack depth
            .stackSize(10)
            // Set uncapped exception handler
            .uncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                @Override
                public void uncaughtException(Thread t, Throwable e) {

                }
            });
    // thread-1
    Thread firstThread = platformThreadBuilder.unstarted(() -> System.out.println("Hello Platform Thread First"));
    // thread-2
    Thread secondThread = platformThreadBuilder.unstarted(() -> System.out.println("Hello Platform Thread Second"));
    Thread.Builder.OfVirtual virtualThreadBuilder = Thread.ofVirtual()
            // Collaborative process name
            .name("fiber-1")
            // Collaboration name prefix + starting self incrementing number = > prefix + start. The next created collaboration name is prefix + (start + 1)
            // If start > 0, the name attribute configuration will be overwritten
            .name("fiber-", 1L)
            // Enable ThreadLocal
            .allowSetThreadLocals(false)
            // Enable InheritableThreadLocal
            .inheritInheritableThreadLocals(false)
            // Set the scheduler, the Executor instance, that is, the scheduler is a thread pool. If it is set to NULL, virtualthread will be used DEFAULT_ SCHEDULER
            .scheduler(null)
            // Set uncapped exception handler
            .uncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                @Override
                public void uncaughtException(Thread t, Throwable e) {

                }
            });
    // fiber-1
    Thread firstFiber = virtualThreadBuilder.unstarted(() -> System.out.println("Hello Platform Virtual First"));
    // fiber-2
    Thread secondFiber = virtualThreadBuilder.unstarted(() -> System.out.println("Hello Platform Virtual Second"));
}

One thing we can find here is that the builder can be reused. If you want to use the builder to create the same batch of threads or coroutines with the same parameters, you can set the name(String prefix, long start) method, define the name prefix of the thread or coroutine and a number greater than or equal to 0, repeatedly call the Builder#unstarted(Runnable task) method to create threads or coroutines in batch, and set the name to prefix + start, prefix + (start + 1) prefix + (start + 2) and so on. The creation of a coroutine is basically as simple as this. When running, directly call the start() method:

public class FiberSample2 {

    public static void main(String[] args) throws Exception {
        Thread.ofVirtual()
                .name("fiber-1")
                .allowSetThreadLocals(false)
                .inheritInheritableThreadLocals(false)
                .unstarted(() -> {
                    Thread fiber = Thread.currentThread();
                    System.out.printf("[%s,daemon:%s,virtual:%s] - Hello World\n", fiber.getName(),
                            fiber.isDaemon(), fiber.isVirtual());
                }).start();
        // Main thread sleep
        Thread.sleep(Long.MAX_VALUE);
    }
}

At present, the above classes cannot be compiled in the mainstream IDE, so they can only be compiled and run using the tools under the JDK directory, as follows:

# Execute - current directory I: \ j-projects \ framework source code \ fiber sample \ SRC \ main \ Java
(1)compile: I:\Environment\Java\jdk-18-loom\bin\javac.exe I:\J-Projects\framework-source-code\fiber-sample\src\main\java\cn\throwx\fiber\sample\FiberSample2.java
(2)implement main method: I:\Environment\Java\jdk-18-loom\bin\java.exe  cn.throwx.fiber.sample.FiberSample2


It can also be seen here that the daemon ID of all collaboration instances is true by default and cannot be modified.

Summary

If you use the room project from an early perspective, you can get a glimpse of how JVM developers develop based on the major feature of collaborative process in advance, which is of great help to improve the interest in learning JDK kernel code. At present, it is estimated that there are still many functions to be improved for the implementation of the collaboration project from the RELEASE version, including the stability of the new API and whether the collaboration can be transplanted to the original JUC class library (the current Loom-JDK-18+9 does not modify the original thread pool and other class libraries), so wait quietly in the process of maintaining attention.
'

Topics: Java Back-end