I’ve been following the development progress of the JDK concurrent library for a long time, but I was busy some time ago and rarely checked the official OpenJDK website.

The Java concurrent project Loom (because the project is still in the development stage, OpenJDK gives only a small amount of Loom project-related information in the official website https://openjdk.java.net/projects/loom) has been established before 2018, and has been released based on JDK17 compilation and JDK18 compilation, etc. Early versions, I only found the JDK18 compiled version when I downloaded the early versions of Loom:

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

Due to the high version of this JDK, you can currently use the mainstream IDE to import Loom-JDK-18+9 for code highlighting and syntax reminder, temporarily can not find a way to compile, temporarily use the JDK execution directory of the javac command script to compile, using the java command script to run.

Brief introduction of Loom project

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

The title of the Loom project already highlights the three main new features introduced.

  • Fibers: A few years ago I read the test code of the Loom project that was using the Fiber API (now this API has been removed), meaning lightweight threads, i.e., concurrent threads, also known as lightweight user threads, amazingly in the current JDK is actually called Virtual Thread (virtual threads)
  • Continuations: straight implementation is a bit like a closed package, reference to a lot of information, have not yet understood its exact meaning, feel that it can be “rough” interpreted as “what the program will execute next” or “the next block of code to be executed”
  • Tail-Calls: VM-level support for tail-calls

Three new features do not expand in detail, only the EA version, there is still the possibility of modification, so there is no need to expand in detail.

Virtual Thread use

The use of concurrency in the current version of the Loom project does not introduce a new public VirtualThread class, although VirtualThread does exist, this class uses the default modifier, is hidden in the java.lang package, and VirtualThread is a subclass of Thread. The API for creating a concurrent thread is located in the Thread class at

Use this API to create coroutines as follows.

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

From the current source code it is known that

  • VirtualThread will get the parent thread’s scheduler through Thread.currentThread(). If it runs in the main method, then the parent thread of the concurrent thread instance in the above code is the main thread
  • The default scheduler is a system-created instance of ForkJoinPool (VirtualThread.DEFAULT_SCHEDULER), and the input Runnable instance is wrapped as a RunContinuation, which is eventually executed by the scheduler
  • For timed unpark (blocking, waiting to wake up), use the system-created ScheduledExecutorService instance to wake up
  • This static factory method runs immediately after the coroutine is created and returns the coroutine instance

If you follow the Thread.startVirtualThread() method above to create a coroutine, you obviously can’t define properties such as the name of the coroutine. the Loom project introduces a builder pattern for the Thread class that solves this problem in a more reasonable way.

1
2
3
4
5
6
7
8
9
// Create a platform thread builder, corresponding to a 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 a Thread instance, where the Platform Thread is actually the thread instance introduced in JDK1.0, the normal user thread
  • The ofVirtual() method is used to build the VirtualThread instance, that is, to build the coroutine instance

The chain of all Setter methods for these two builder instances expands as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public static void main(String[] args) {
    Thread.Builder.OfPlatform platformThreadBuilder = Thread.ofPlatform()
            // Is it a daemon thread
            .daemon(true)
            // Thread Group
            .group(Thread.currentThread().getThreadGroup())
            // Thread Name
            .name("thread-1")
            // thread name prefix + start number => prefix + start, the next created thread name is prefix + (start + 1)
            // The name attribute is overridden if start > 0
            .name("thread-", 1L)
            // Whether to enable ThreadLocal
            .allowSetThreadLocals(false)
            // Whether to enable InheritableThreadLocal
            .inheritInheritableThreadLocals(false)
            // Set priority
            .priority(100)
            // Set the thread stack depth
            .stackSize(10)
            // Setting the Uncaught 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()
            // coroutine name
            .name("fiber-1")
            // coroutine name prefix + start self-incrementing number => prefix + start, the next coroutine name created is prefix + (start + 1)
            // The name attribute is overridden if start > 0
            .name("fiber-", 1L)
            // Whether to enable ThreadLocal
            .allowSetThreadLocals(false)
            // Whether to enable InheritableThreadLocal
            .inheritInheritableThreadLocals(false)
            // Set the scheduler, the Executor instance, that is, the scheduler is a pool of threads, set to NULL will use VirtualThread.
            .scheduler(null)
            // Setting the Uncaught 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 that can be found here is that the builder is reusable. If you want to use the builder to create the same batch of threads or concurrent threads with the same parameter settings, you can set the name(String prefix, long start) method to define the name prefix of the thread or concurrent thread and a number greater than or equal to 0. Call the Builder#unstarted(Runnable task) method repeatedly to create a batch of The name of the thread or concurrent thread is set to prefix + start, prefix + (start + 1), prefix + (start + 2), and so on. The creation of a thread is basically as simple as this, and the start() method is called directly if it is running.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
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);
    }
}

Currently it is not possible to compile the above classes in the mainstream IDE, so they can only be compiled and run using the tools in that JDK directory as follows.

1
2
3
# Execute - current directory I:\J-Projects\framework-source-code\fiber-sample\src\main\java
(1)Compilation: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)Execute main method:I:\Environment\Java\jdk-18-loom\bin\java.exe  cn.throwx.fiber.sample.FiberSample2

Here you can also see that the daemon flag of all concurrent instances is true by default and cannot be modified.

Summary

If you use the Loom project from a tasting perspective, you can get an early peek at how JVM developers are developing based on the major feature of concurrency, which can help a lot to increase interest in learning the JDK kernel code. From the current point of view, the implementation of the Loom project for the concurrency RELEASE version is estimated to have a lot of features to improve, including the stability of the new API, and whether the concurrency can be ported to the original JUC class library to use (the current Loom-JDK-18+9 did not modify the original thread pool and other class libraries) and other issues need to be resolved, so in the process of maintaining concern quietly wait for it.


Reference https://www.throwx.cn/2021/08/21/learn-about-loom-project/