Java线程之虚拟线程VirtualThread

一、概述

虚拟线程(Virtual Threads)是轻量级线程,可以减少编写、维护和调度高吞吐量并发应用程序的工作量。

1.1 内部线程实现模式

线程是可供调度的最小处理单元,它与其他类似的处理单元并发运行,并且在很大程度上是独立运行的。线程有两种:

  • 绿色线程(Green Thread:远古时期,Java使用绿色线程模式。这个模式下,多线程的调度和管理有JVM完成。绿色线程模式才作用M:1线程映射模型。这里就有一个问题,Java不能够规模化管理这种线程,也就无法充分发挥硬件性能。同样的实现绿色线程也是一件非常有挑战性的事情,因为它需要非常底层的支持才能够良好运行。随后Java移除了绿色线程,转而使用本地线程。这使得Java的线程执行比绿色线程更慢。
  • 平台线程(Platform Thread:从Java 1.2开始从绿色线程切换到了平台线程模式(有些人称之为本地线程(Native Thread))。在操作系统的帮助下,JVM得以控制平台线程。平台线程的执行效率很高,但是开启和关闭他们的资源消耗较大。这就是为什么我们现在要使用线程池。这个模型遵循着1:1线程映射,即一个Java线程映射到一个内核线程。当一个Java线程被创建时,相应的一个对应的核心线程也会被创建,用来执行线程代码。自此之后,平台线程模型的做法就延续到了今天。

Java19开始,Java引入了虚拟线程模式。虚拟线程模式下,JVM只负责管理虚拟线程,而不管理平台线程。虚拟线程的创建和销毁都由JVM完成,因此虚拟线程的创建和销毁效率很高。虚拟线程的调度由JVM完成,因此虚拟线程的调度效率很高。虚拟线程的调度采用的是抢占式调度,即当一个虚拟线程被阻塞时,调度器会将其他的虚拟线程唤醒,让他们运行。虚拟线程的栈空间是虚拟的,因此虚拟线程的栈空间大小可以根据需要动态调整。虚拟线程的实现方式是通过Thread.Builder.OfVirtual对象进行创建的,该对象可以创建虚拟线程,并可以设置线程的名称、优先级、守护线程、线程组等属性。

1.2 应用场景

在服务器端的应用程序中,可能会有大量的并发任务需要执行,而虚拟线程能够明显的提高应用的吞吐量。下面的场景能够显著的提高程序的吞吐量:

  • 大批量的处理时间较短的计算任务
  • 大量的IO阻塞等待处理
  • thread-per-request风格的应用程序,例如主流的Tomcat线程模型或者基于类似线程模型实现的SpringMVC框架等等

下面代码中为每个任务创建一个线程,当任务量较多的时候,你的电脑可以感受到明显的卡顿(如果没有,可以增加任务数量试下):

public class Test1 {
    public static void main(String[] args) {
        //ExecutorService实现了AutoCloseable接口,可以自动关闭了
        try (ExecutorService executor = Executors.newCachedThreadPool()) {
            //向executor中提交1000000个任务
            IntStream.range(0, 1000000).forEach(i -> {
                executor.submit(() -> {
                    try {
                        //睡眠1秒,模拟耗时操作
                        Thread.sleep(Duration.ofSeconds(1));
                        System.out.println("执行任务:" + i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                });
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

将上面的代码改成虚拟线程之后,电脑不会感受到卡顿了:

public class Test2 {
    public static void main(String[] args) {
        //newVirtualThreadPerTaskExecutor为每个任务创建一个虚拟线程
        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
            IntStream.range(0, 1000000).forEach(i -> {
                executor.submit(() -> {
                    try {
                        //睡眠1秒,模拟耗时操作
                        Thread.sleep(Duration.ofSeconds(1));
                        System.out.println("执行任务:" + i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            });
        }
    }
}

1.3 虚拟线程和平台线程区别

从内存空间上来说,虚拟线程的栈空间可以看作是一个大块的栈对象,它被存储在了java堆中,相比于单独存储对象,堆中存储虚拟线程的栈会造成一些空间的浪费,这点在后续的java版本中应该会得到改善,当然这样也是有一些好处的,就是可以重复利用这部分栈空间,不用多次申请开辟新的内存地址。虚拟线程的栈空间最大可以达到平台线程的栈空间容量。

虚拟线程并不是GC root,其中的引用不会出现stop-world,当虚拟线程被阻塞之后比如BlockingQueue.take(),平台线程既不能获取到虚拟线程,也不能获取到queue队列,这样该平台线程可能会被回收掉,虚拟线程在运行或阻塞时不会被GC

总结以下几点:

  • 通过Thread构造方法创建的线程都是平台线程
  • 虚拟线程是守护线程,不能通过setDaemon方法改成非守护线程
  • 虚拟线程的优先级是默认的5,不能被修改,将来的版本可能允许修改
  • 虚拟线程不支持stop()suspend()resume()方法

二、虚拟线程

2.1 实现原理

虚拟线程是一种轻量级(用户模式)线程,这种线程是由Java虚拟机调度,而不是操作系统。虚拟线程占用空间小,任务切换开销几乎可以忽略不计,因此可以极大量地创建和使用。总体来看,虚拟线程实现如下:

\[virtual thread = continuation + scheduler \]

虚拟线程会把任务(一般是java.lang.Runnable)包装到一个Continuation实例中:

  • 当任务需要阻塞挂起的时候,会调用Continuationyield操作进行阻塞
  • 当任务需要解除阻塞继续执行的时候,Continuation会被继续执行

Scheduler也就是执行器,会把任务提交到一个载体线程池中执行:

  • 执行器是java.util.concurrent.Executor的子类
  • 虚拟线程框架提供了一个默认的ForkJoinPool用于执行虚拟线程任务

下文会把carrier thread称为"载体线程",指的是负责执行虚拟线程中任务的平台线程,或者说运行虚拟线程的平台线程称为它的载体线程

操作系统调度系统线程,而Java平台线程与系统线程一一映射,所以平台线程被操作系统调度,但是虚拟线程是由JVM调度。JVM把虚拟线程分配给平台线程的操作称为mount(挂载),反过来取消分配平台线程的操作称为unmount(卸载):

  • mount操作:虚拟线程挂载到平台线程,虚拟线程中包装的Continuation栈数据帧或者引用栈数据会被拷贝到平台线程的线程栈,这是一个从堆复制到栈的过程
  • unmount操作:虚拟线程从平台线程卸载,大多数虚拟线程中包装的Continuation栈数据帧会留在堆内存中

这个mount -> run -> unmount过程用伪代码表示如下:

mount();
try {
    Continuation.run();
} finally {
    unmount();
}

JVM使用M:N来完成虚拟线程与本地线程的映射。

2.2 虚拟线程和线程池区别

看上去虚拟线程和线程池有类似之处,都是利用M个内核线程,完成N个任务,而避免平台线程频繁的创建和销毁。但他们是有本质区别的:

  • 线程池中的正在执行的任务只有到任务执行完成后,才会释放平台线程,如果某个任务在执行过程中发生IO阻塞也不会被挂起执行其他任务。
  • 虚拟线程中运行的代码调用阻塞I/O操作时,Java运行时会挂起虚拟线程,然后切换到另一个可执行的虚拟线程,直到它可以恢复为止。

三、源码分析

3.1 Continuation

Continuation直译为"连续",一般来说表示一种语言构造,「使语言可以在任意点保存执行状态并且在之后的某个点返回」。在JDK中对应类jdk.internal.vm.Continuation,这个类只有一句类注释A one-shot delimited continuation,直译为「一个只能执行一次的回调函数」。由于Continuation的成员和方法缺少详细的注释,并且大部分功能由JVM实现,这里只能阅读其一些骨干源码和上一小节编写的Continuation相关例子去了解其实现。先看成员变量和构造函数:

public class Continuation {
    // 判断是否需要保留当前线程的本地缓存,由系统参数jdk.preserveExtentLocalCache决定
    private static final boolean PRESERVE_SCOPED_VALUE_CACHE;

    // 真正要被执行的任务实例
    private final Runnable target;

    // 标识Continuation的范围,
    private final ContinuationScope scope;

    // Continuation的父节点,如果为空的时候则为本地线程栈
    private Continuation parent;

    // Continuation的子节点,非空时候说明在子Continuation中进行了yield操作
    private Continuation child;

    // 猜测为Continuation栈结构,由JVM管理,无法得知其真实作用
    private StackChunk tail;

    // 标记Continuation是否已经完成
    private boolean done;

    // 标记是否进行了mount操作
    private volatile boolean mounted = false;

    // yield操作时候设置的信息
    private Object yieldInfo;

    // 标记一个未挂载的Continuation是否通过强制抢占式卸载
    private boolean preempted;

    // 保留当前线程的本地缓存的副本
    private Object[] extentLocalCache;

    // 构造函数,要求传入范围和任务包装实例
    public Continuation(ContinuationScope scope, Runnable target) {
        this.scope = scope;
        this.target = target;
    }
}

Continuation是一个双向链表设计,它的唯一一组构造参数是ContinuationScopeRunnable

这里不深入研究内部StackChunkPinned等实现,直接看runenter系列方法和yield方法:

public class Continuation {
    // Continuation.run()
    public final void run() {
        // 设置死循环
        while (true) {
            // 进行mount操作
            mount();
            JLA.setExtentLocalCache(extentLocalCache);
            // 如果Continuation已完成则抛出异常
            if (done)
                throw new IllegalStateException("Continuation terminated");
            // 获取当前虚拟线程分配的运载线程
            Thread t = currentCarrierThread();
            if (parent != null) {
                if (parent != JLA.getContinuation(t))
                    throw new IllegalStateException();
            } else
                this.parent = JLA.getContinuation(t);
            // 运载线程设置当前Continuation实例
            JLA.setContinuation(t, this);

            try {
                // 判断ContinuationScope是否虚拟线程范围
                boolean isVirtualThread = (scope == JLA.virtualThreadContinuationScope());
                if (!isStarted()) { // is this the first run? (at this point we know !done)
                    // 激活enter系列方法,标记isContinue为false,标记是否虚拟线程范围
                    enterSpecial(this, false, isVirtualThread);
                } else {
                    assert !isEmpty();
                    // 激活enter系列方法,标记isContinue为true,标记是否虚拟线程范围
                    enterSpecial(this, true, isVirtualThread);
                }
            } finally {
                // 设置内存屏障
                fence();
                try {
                    assert isEmpty() == done
                            : "empty: " + isEmpty() + " done: " + done + " cont: "
                            + Integer.toHexString(System.identityHashCode(this));
                    // 当前Continuation执行完成后,把运载线程的Continuation指向父Continuation
                    JLA.setContinuation(currentCarrierThread(), this.parent);
                    if (parent != null)
                        parent.child = null;
                    // 进行后置的yield清理工作
                    postYieldCleanup();
                    // 进行unmount操作
                    unmount();
                    // 判断是否需要保留当前线程的本地缓存并处理
                    if (PRESERVE_SCOPED_VALUE_CACHE) {
                        extentLocalCache = JLA.extentLocalCache();
                    } else {
                        extentLocalCache = null;
                    }
                    JLA.setExtentLocalCache(null);
                } catch (Throwable e) {
                    e.printStackTrace();
                    System.exit(1);
                }
            }
            // we're now in the parent continuation
            assert yieldInfo == null || yieldInfo instanceof ContinuationScope;
            // 父Continuation的yieldInfo缓存当前的scope实例,清空当前Continuation的父节点和yieldInfo
            if (yieldInfo == null || yieldInfo == scope) {
                this.parent = null;
                this.yieldInfo = null;
                // 这个位置是死循环的唯一跳出点
                return;
            } else {
                // 执行到这个位置说明在当前是子Continuation并且进行了yield操作,需要跳转到父Continuation进行yield操作
                parent.child = this;
                parent.yield0((ContinuationScope) yieldInfo, this);
                parent.child = null;
            }
        }
    }

    // Continuation.enter()系列方法

    // 这是一个native方法,它最终会根据判断回调到enter()方法
    private native static void enterSpecial(Continuation c, boolean isContinue, boolean isVirtualThread);

    // Continuation的入口方法,用户任务回调的入口
    @DontInline
    @IntrinsicCandidate
    private static void enter(Continuation c, boolean isContinue) {
        // This method runs in the "entry frame".
        // A yield jumps to this method's caller as if returning from this method.
        try {
            c.enter0();
        } finally {
            c.finish();
        }
    }

    // 真正任务包装器执行的回调方法
    private void enter0() {
        target.run();
    }

    // Continuation完成,标记done为true
    private void finish() {
        done = true;
        assert isEmpty();
    }

    // Continuation.yield()方法,静态方法
    public static boolean yield(ContinuationScope scope) {
        // 获取当前运载线程的Continuation实例
        Continuation cont = JLA.getContinuation(currentCarrierThread());
        Continuation c;
        // 基于Continuation实例当前向父节点遍历,直到匹配虚拟线程类型的ContinuationScope的Continuation,
        // 如果没有匹配的Continuation会抛出异常中断流程
        for (c = cont; c != null && c.scope != scope; c = c.parent)
            ;
        if (c == null)
            throw new IllegalStateException("Not in scope " + scope);
        // 把当前的Continuation挂起到给定的ContinuationScope
        return cont.yield0(scope, null);
    }

    // 通过上下文猜测是当前的Continuation实例挂起到给定的ContinuationScope
    private boolean yield0(ContinuationScope scope, Continuation child) {
        // 强制抢占式卸载标记为false
        preempted = false;
        // 如果当前Continuation实例的yieldInfo不等于传入的ContinuationScope实例,
        // 则进行更新,相等的情况下yieldInfo会保持是一个空值
        if (scope != this.scope)
            this.yieldInfo = scope;
        // 最终的yield调用,最终当前Continuation就是阻塞在此方法,
        // 从下文源码猜测,当该方法唤醒后,res值为0的时候,当前Continuation实例会继续执行,
        // 返回其他值的时候则会打印pined线程栈
        int res = doYield();
        // 放置内存屏障防止指令重排,后面注释提到是防止编译器进行某些转换
        U.storeFence(); // needed to prevent certain transformations by the compiler

        assert scope != this.scope
                || yieldInfo == null
                : "scope: " + scope + " this.scope: " + this.scope
                + " yieldInfo: " + yieldInfo + " res: " + res;
        assert yieldInfo == null
                || scope == this.scope
                || yieldInfo instanceof Integer
                : "scope: " + scope + " this.scope: " + this.scope
                + " yieldInfo: " + yieldInfo + " res: " + res;

        if (child != null) { // TODO: ugly <----- 这个位置还有一句吐槽的代码注释:丑陋的代码
            if (res != 0) {
                child.yieldInfo = res;
            } else if (yieldInfo != null) {
                assert yieldInfo instanceof Integer;
                child.yieldInfo = yieldInfo;
            } else {
                child.yieldInfo = res;
            }
            this.yieldInfo = null;
        } else {
            if (res == 0 && yieldInfo != null) {
                res = (Integer) yieldInfo;
            }
            this.yieldInfo = null;

            if (res == 0)
                // Continuation实例继续执行前回调
                onContinue();
            else
                // Continuation固定在运载线程前回调,res是pined的级别
                onPinned0(res);
        }
        assert yieldInfo == null;
        // 返回布尔值结果表示当前Continuation实例是否会继续执行
        return res == 0;
    }

    // 最终的yield调用,看实现是抛出异常,猜测是由JVM实现
    @IntrinsicCandidate
    private static native int doYield();
}

说实话,Continuation源码的可读性比想象中低,连代码注释也留下了"丑陋的"这句吐槽。通过上面源码分析和上一节Continuation的一个例子,可以得知Continuation#yield()可以让程序代码中断,然后再次调用Continuation#run()可以从上一个中断位置继续执行,JVM在这个过程中为使用者屏蔽了Continuation和运行此Continuation的平台线程之间的交互细节,让使用者可以专注实际的任务开发即可。

3.2 VirtualThread

前面花了不少篇幅介绍Continuation,它是一个全新的API。已有的JUC类库已经十分完善,如果可以把Continuation融入到已有的JUC体系,那么就可以通过线程池技术去管理运载线程,原有的大多数并发相关API也能直接在协程体系中使用。从这个背景来看,创造一个Thread类的全新子类用于融合JUCContinuation是十分合适的,这样通过很小的改造成本就能通过Java继承特性把这个全新子类适配JUC体系,也能扩展一些API让它适配协程新引入的特性,这个全新的子类就是java.lang.VirtualThread

VirtualThread类的继承体系如下:

package java.lang;

final class VirtualThread extends BaseVirtualThread {
    // ...
}

package java.lang;

sealed abstract class BaseVirtualThread extends Thread
        permits VirtualThread, ThreadBuilders.BoundVirtualThread {
    // ... 
}

VirtualThreadBaseVirtualThread的子类,而BaseVirtualThread是一个"密封类",它是Thread的子类,只对VirtualThreadThreadBuilders.BoundVirtualThread开放,并且VirtualThread是「包私有访问权限的」同时用final关键字修饰,无法被继承。接着看VirtualThread的成员变量和构造函数:

// java.lang.VirtualThread
final class VirtualThread extends BaseVirtualThread {

    // Unsafe实例
    private static final Unsafe U = Unsafe.getUnsafe();

    // 虚拟线程的ContinuationScope静态常量
    private static final ContinuationScope VTHREAD_SCOPE = new ContinuationScope("VirtualThreads");

    // 调度器,或者说执行器,默认就是用此调度器运行虚拟线程
    private static final ForkJoinPool DEFAULT_SCHEDULER = createDefaultScheduler();

    // 调度线程池实例,用于唤醒带超时阻塞的虚拟线程实例,主要用于sleep的唤醒
    private static final ScheduledExecutorService UNPARKER = createDelayedTaskScheduler();

    // pin模式,也就是pined thread的跟踪模式,决定打印堆栈的详细程度,
    // 来自于系统参数jdk.tracePinnedThreads,full表示详细,short表示简略
    private static final int TRACE_PINNING_MODE = tracePinningMode();

    // 下面几个都是成员地址,用于Unsafe直接操作成员
    private static final long STATE = U.objectFieldOffset(VirtualThread.class, "state");
    private static final long PARK_PERMIT = U.objectFieldOffset(VirtualThread.class, "parkPermit");
    private static final long CARRIER_THREAD = U.objectFieldOffset(VirtualThread.class, "carrierThread");
    private static final long TERMINATION = U.objectFieldOffset(VirtualThread.class, "termination");

    // 调度器实例
    private final Executor scheduler;

    // Continuation实例
    private final Continuation cont;

    // Continuation实例的Runnable包装实例
    private final Runnable runContinuation;

    // 虚拟线程状态,这个值由JVM访问和修改
    private volatile int state;

    // 下面的状态集合
    private static final int NEW = 0;
    private static final int STARTED = 1;
    private static final int RUNNABLE = 2;     // runnable-unmounted
    private static final int RUNNING = 3;     // runnable-mounted
    private static final int PARKING = 4;
    private static final int PARKED = 5;     // unmounted
    private static final int PINNED = 6;     // mounted
    private static final int YIELDING = 7;     // Thread.yield
    private static final int TERMINATED = 99;  // final state

    // 虚拟线程unmount后可以从调度过程中挂起的状态
    private static final int SUSPENDED = 1 << 8;
    private static final int RUNNABLE_SUSPENDED = (RUNNABLE | SUSPENDED);
    private static final int PARKED_SUSPENDED = (PARKED | SUSPENDED);

    // park操作许可
    private volatile boolean parkPermit;

    // 运载线程实例
    private volatile Thread carrierThread;

    // 终结倒数栅栏实例,主要用于join操作
    private volatile CountDownLatch termination;

    // 唯一构造函数
    VirtualThread(Executor scheduler, String name, int characteristics, Runnable task) {
        // 默认标记bound为false,当bound为true的时候标记为绑定到系统线程
        super(name, characteristics, /*bound*/ false);
        Objects.requireNonNull(task);
        // 如果传入的调度器实例非空则直接使用
        // 否则,如果父线程是虚拟线程,则使用父虚拟线程的调度器实例
        // 如果传入的调度器实例为空,父线程为平台线程,那么使用默认的调度器
        // choose scheduler if not specified
        if (scheduler == null) {
            Thread parent = Thread.currentThread();
            if (parent instanceof VirtualThread vparent) {
                scheduler = vparent.scheduler;
            } else {
                scheduler = DEFAULT_SCHEDULER;
            }
        }
        // 赋值调度器
        this.scheduler = scheduler;
        // 封装和初始化Continuation
        this.cont = new VThreadContinuation(this, task);
        // 初始化Continuation的Runnable包装器,最终提交到调度器中执行
        this.runContinuation = this::runContinuation;
    }

    // 虚拟线程Continuation的专有子类,默认为ContinuationScope("VirtualThreads"),
    // 从而实现Continuation.enter()执行时候实际上执行的是VirtualThread.run()方法
    // 也就是 Runnable.run()[runContinuation by carrier thread from executor] 
    //          --> Continuation.run() 
    //              --> Continuation.enter() 
    //                  --> VirtualThread.run()
    //                      -->  Runnable.run()[user task]
    private static class VThreadContinuation extends Continuation {
        VThreadContinuation(VirtualThread vthread, Runnable task) {
            super(VTHREAD_SCOPE, () -> vthread.run(task));
        }

        // pin之前回调的方法,基于TRACE_PINNING_MODE的返回值决定pinned线程栈的打印详略
        @Override
        protected void onPinned(Continuation.Pinned reason) {
            if (TRACE_PINNING_MODE > 0) {
                boolean printAll = (TRACE_PINNING_MODE == 1);
                PinnedThreadPrinter.printStackTrace(System.out, printAll);
            }
        }
    }

    // 在当前线程上运行或继续Continuation的执行,必须由平台线程运行此方法,
    // 最终会封装为Runnable包装器提交到执行器中运行
    private void runContinuation() {
        // the carrier must be a platform thread
        if (Thread.currentThread().isVirtual()) {
            throw new WrongThreadException();
        }

        // set state to RUNNING
        boolean firstRun;
        int initialState = state();
        // 当前为STARTED状态并且CAS更新为RUNNING状态则标记首次运行为true
        if (initialState == STARTED && compareAndSetState(STARTED, RUNNING)) {
            // first run
            firstRun = true;
        } else if (initialState == RUNNABLE && compareAndSetState(RUNNABLE, RUNNING)) {
            // 当前为RUNNABLE状态并且CAS更新为RUNNING状态则标记首次运行为false,并且设置park许可为false
            // consume parking permit
            setParkPermit(false);
            firstRun = false;
        } else {
            // not runnable
            return;
        }

        // notify JVMTI before mount
        if (notifyJvmtiEvents) notifyJvmtiMountBegin(firstRun);

        try {
            // 执行Continuation.run()
            cont.run();
        } finally {
            // Continuation执行完成,回调钩子方法afterTerminate
            if (cont.isDone()) {
                afterTerminate(/*executed*/ true);
            } else {
                // Continuation没有执行完成,说明调用了Continuation.yield或者pin到运载线程中进行了park操作
                afterYield();
            }
        }
    }

    // Continuation执行完成回调的钩子方法
    private void afterTerminate(boolean executed) {
        assert (state() == TERMINATED) && (carrierThread == null);

        if (executed) {
            if (notifyJvmtiEvents) notifyJvmtiUnmountEnd(true);
        }

        // 如果有其他线程阻塞等待虚拟线程的返回,例如调用了join方法,那么在这里解除阻塞
        CountDownLatch termination = this.termination;
        if (termination != null) {
            assert termination.getCount() == 1;
            termination.countDown();
        }

        // 如果执行成功则通知线程容器当前线程实例退出,清空线程本地变量引用
        if (executed) {
            // notify container if thread executed
            threadContainer().onExit(this);

            // clear references to thread locals
            clearReferences();
        }
    }

    // 由于Continuation的yield操作或者调用了Thread.yield()导致Continuation挂起,
    // 需要重新把Continuation的包装器"懒提交"到调度器中
    private void afterYield() {
        int s = state();
        assert (s == PARKING || s == YIELDING) && (carrierThread == null);
        // 如果是PARKING状态,这种对应于Continuation的yield操作调用
        if (s == PARKING) {
            // 更变为PARKED状态
            setState(PARKED);

            // notify JVMTI that unmount has completed, thread is parked
            if (notifyJvmtiEvents) notifyJvmtiUnmountEnd(false);

            // 得到park许可,并且CAS为RUNNABLE状态
            if (parkPermit && compareAndSetState(PARKED, RUNNABLE)) {
                // 进行懒提交,如果可能的话,用当前线程作为运载线程继续执行任务
                lazySubmitRunContinuation();
            }
        } else if (s == YIELDING) {   // 如果是YIELDING状态,这种对应于调用了Thread.yield
            // 更变为RUNNABLE状态
            setState(RUNNABLE);

            // notify JVMTI that unmount has completed, thread is runnable
            if (notifyJvmtiEvents) notifyJvmtiUnmountEnd(false);

            // 进行懒提交,如果可能的话,用当前线程作为运载线程继续执行任
            lazySubmitRunContinuation();
        }
    }
}

这里唯一的构造函数是比较复杂的,抛开一些钩子接口,最终想达到的效果就是:

Runnable.run()[runContinuation by carrier thread from executor] 
    --> Continuation.run()
        --> Continuation.enter()
            --> VirtualThread.run()
                --> Runnable.run()[user task]

用户任务实际被包裹了很多层,在最里面一层才会回调。VirtualThread中提供了两个静态全局的线程池实例,一个用于调度,一个用于唤醒,这里看看两个线程池是如何构造的:

// java.lang.VirtualThread
final class VirtualThread extends BaseVirtualThread {

    private static final ForkJoinPool DEFAULT_SCHEDULER = createDefaultScheduler();
    private static final ScheduledExecutorService UNPARKER = createDelayedTaskScheduler();

    // 创建默认的调度器
    private static ForkJoinPool createDefaultScheduler() {
        // 线程工厂,默认创建CarrierThread实例,CarrierThread是ForkJoinWorkerThread的一个子类
        ForkJoinWorkerThreadFactory factory = pool -> {
            PrivilegedAction<ForkJoinWorkerThread> pa = () -> new CarrierThread(pool);
            return AccessController.doPrivileged(pa);
        };
        PrivilegedAction<ForkJoinPool> pa = () -> {
            int parallelism, maxPoolSize, minRunnable;
            String parallelismValue = System.getProperty("jdk.virtualThreadScheduler.parallelism");
            String maxPoolSizeValue = System.getProperty("jdk.virtualThreadScheduler.maxPoolSize");
            String minRunnableValue = System.getProperty("jdk.virtualThreadScheduler.minRunnable");
            if (parallelismValue != null) {
                parallelism = Integer.parseInt(parallelismValue);
            } else {
                parallelism = Runtime.getRuntime().availableProcessors();
            }
            if (maxPoolSizeValue != null) {
                maxPoolSize = Integer.parseInt(maxPoolSizeValue);
                parallelism = Integer.min(parallelism, maxPoolSize);
            } else {
                maxPoolSize = Integer.max(parallelism, 256);
            }
            if (minRunnableValue != null) {
                minRunnable = Integer.parseInt(minRunnableValue);
            } else {
                minRunnable = Integer.max(parallelism / 2, 1);
            }
            Thread.UncaughtExceptionHandler handler = (t, e) -> {
            };
            boolean asyncMode = true; // FIFO
            return new ForkJoinPool(parallelism, factory, handler, asyncMode,
                    0, maxPoolSize, minRunnable, pool -> true, 30, SECONDS);
        };
        return AccessController.doPrivileged(pa);
    }

    // 创建调度线程池,用于虚拟线程带超时时间的unpark操作
    private static ScheduledExecutorService createDelayedTaskScheduler() {
        String propValue = GetPropertyAction.privilegedGetProperty("jdk.unparker.maxPoolSize");
        int poolSize;
        if (propValue != null) {
            poolSize = Integer.parseInt(propValue);
        } else {
            // 确保至少有一个工作线程
            poolSize = 1;
        }
        ScheduledThreadPoolExecutor stpe = (ScheduledThreadPoolExecutor)
                Executors.newScheduledThreadPool(poolSize, task -> {
                    return InnocuousThread.newThread("VirtualThread-unparker", task);
                });
        // 任务取消后马上从工作队列移除
        stpe.setRemoveOnCancelPolicy(true);
        return stpe;
    }
}

对于默认调度器(DEFAULT_SCHEDULER)的创建,它是一个ForkJoinPool实例,构造参数的选取如下:

  • parallelism参数由系统变量jdk.virtualThreadScheduler.parallelism决定,默认值为Runtime.getRuntime().availableProcessors(),如果配置了系统参数jdk.virtualThreadScheduler.maxPoolSize则取min(parallelism,maxPoolSize)
  • maxPoolSize参数由系统变量jdk.virtualThreadScheduler.maxPoolSize决定,默认值为min(parallelism, maxPoolSize)
  • minRunnable参数由系统变量jdk.virtualThreadScheduler.minRunnable决定,默认值为max(parallelism / 2, 1)
  • asyncMode参数固定值true,也就是选用FIFO模式
  • keepAliveTime参数为固定值30
  • saturate参数在JDK17引入,是一个Predicate函数,在此固定返回true,用于忽略minRunnable值允许线程池饱和
  • 线程工厂用于创建CarrierThread实例,CarrierThreadForkJoinWorkerThread的子类

在Intel 4C8T开发机器环境中,该ForkJoinPool实例创建时候的几个参数分别为:parallelism = 8, maxPoolSize = 256, minRunnable = 4。

对于调度线程池(UNPARKER)的创建,它是一个ScheduledThreadPoolExecutor实例,构造参数的选取如下:

  • corePoolSize参数由系统变量jdk.unparker.maxPoolSize决定,并且确保最小值为1
  • 线程工厂用于创建InnocuousThread实例,线程名称为VirtualThread-unparker

接着看虚拟线程的启动方法start():

// java.lang.VirtualThread
final class VirtualThread extends BaseVirtualThread {
    @Override
    public void start() {
        start(ThreadContainers.root());
    }

    // 调度虚拟线程让之运行
    @Override
    void start(ThreadContainer container) {
        // CAS由NEW转换为STARTED状态
        if (!compareAndSetState(NEW, STARTED)) {
            throw new IllegalThreadStateException("Already started");
        }

        // 绑定当前虚拟线程到线程容器
        setThreadContainer(container);

        // 标记为未启动
        boolean started = false;
        // 回调start钩子方法
        container.onStart(this); // may throw
        try {
            // 从给定容器继承extent-local绑定参数
            inheritExtentLocalBindings(container);
            // 提交'runContinuation'任务到调度器
            submitRunContinuation();
            // 标记为启动完成
            started = true;
        } finally {
            // 如果启动失败,则标记最终状态和回调终结钩子方法
            if (!started) {
                setState(TERMINATED);
                container.onExit(this);
                afterTerminate(/*executed*/ false);
            }
        }
    }

    // 提交'runContinuation'任务到调度器
    private void submitRunContinuation() {
        submitRunContinuation(false);
    }

    // 提交'runContinuation'任务到调度器,lazySubmit参数决定是否"懒提交"
    private void submitRunContinuation(boolean lazySubmit) {
        try {
            if (lazySubmit && scheduler instanceof ForkJoinPool pool) {
                // ForkJoinPool类型调度器并且lazySubmit为true,
                // 对runContinuation这个Runnable实例适配为ForkJoinTask类型,进行"懒提交"到ForkJoinPool
                pool.lazySubmit(ForkJoinTask.adapt(runContinuation));
            } else {
                // 非ForkJoinPool类型调度器或者lazySubmit为false,直接使用Executor.execute()提交任务
                scheduler.execute(runContinuation);
            }
        } catch (RejectedExecutionException ree) {
            // 线程池拒绝接收任务,发布提交失败事件到JVM
            var event = new VirtualThreadSubmitFailedEvent();
            if (event.isEnabled()) {
                event.javaThreadId = threadId();
                event.exceptionMessage = ree.getMessage();
                event.commit();
            }
            throw ree;
        }
    }
}

ForkJoinPool#lazySubmit()JDK19新增的一个API,它的方法注释如下:

提交给定的任务,但不保证它最终会在没有可用活动线程的情况下执行。在某些上下文中,这种方法可以通过依赖于特定于上下文的知识来减少竞争和开销,即现有线程(如果在此池中操作,则可能包括调用线程)最终将可用来执行任务

使用此方法提交的目的就是希望可以用当前调用线程去执行任务,对于首次提交Continuation任务可能作用不明显,但是对于Continuation.yield()调用后的再次提交意义比较重大,因为这样就可以「把运行的Continuation.run()方法链分配到同一个运载线程实例」,在开发者的角度就是虚拟线程任务执行中断后恢复执行,执行任务的运载线程没有改变。

源码中还可以发现,run()方法覆盖了Thread#run()替换为空实现,因为VirtualThread最终是触发Continuation#run(),这一点已经在start()方法进行提交和调度。最后分析虚拟线程的阻塞(不带超时,也就是timeout = 0)、限时阻塞(timeout > 0)、join的实现。先看相对简单的joinNanos()

// java.lang.VirtualThread
// Thread.join() --> VirtualThread.joinNanos()
final class VirtualThread extends BaseVirtualThread {
    // 虚拟线程join调用
    boolean joinNanos(long nanos) throws InterruptedException {
        // 如果状态为TERMINATED直接返回true
        if (state() == TERMINATED)
            return true;
        // 获取数栅栏实例
        CountDownLatch termination = getTermination();
        // 再次验证如果状态为TERMINATED直接返回true
        if (state() == TERMINATED)
            return true;

        // 如果nanos为0则调用CountDownLatch.await()阻塞
        if (nanos == 0) {
            termination.await();
        } else {
            // 如果nanos大于0则调用CountDownLatch.await(nanos,TimeUnit)限时阻塞
            boolean terminated = termination.await(nanos, NANOSECONDS);
            if (!terminated) {
                // 阻塞到超时时限过了返回,非解除阻塞下的正常返回
                return false;
            }
        }
        assert state() == TERMINATED;
        // 解除阻塞下的正常返回
        return true;
    }

    // 懒创建终结倒数栅栏实例,设置资源值为1,这里用到CAS是考虑之前已经创建和保存到成员变量,
    // 如果已创建则直接选用成员变量的那个实例
    private CountDownLatch getTermination() {
        CountDownLatch termination = this.termination;
        if (termination == null) {
            termination = new CountDownLatch(1);
            if (!U.compareAndSetReference(this, TERMINATION, null, termination)) {
                termination = this.termination;
            }
        }
        return termination;
    }
}

接着看虚拟线程阻塞和限时阻塞的现实:

// java.lang.VirtualThread
// Thread.sleep() --> VirtualThread.sleepNanos()
final class VirtualThread extends BaseVirtualThread {
    // 给定休眠时间让当前虚拟线程休眠
    void sleepNanos(long nanos) throws InterruptedException {
        assert Thread.currentThread() == this;
        // nanos必须大于等于0
        if (nanos >= 0) {
            // 如果支持线程休眠事件发布则在休眠处理前后处理休眠事件,最终的休眠操作调用doSleepNanos()完成
            if (ThreadSleepEvent.isTurnedOn()) {
                ThreadSleepEvent event = new ThreadSleepEvent();
                try {
                    event.time = nanos;
                    event.begin();
                    doSleepNanos(nanos);
                } finally {
                    event.commit();
                }
            } else {
                doSleepNanos(nanos);
            }
        }
    }

    // 让当前线程休眠给定的睡眠时间(单位为纳秒)。如果nanos为0时,线程将尝试yield
    private void doSleepNanos(long nanos) throws InterruptedException {
        assert nanos >= 0;
        // 响应中断清理中断状态,抛出中断异常
        if (getAndClearInterrupt())
            throw new InterruptedException();
        if (nanos == 0) {
            // nanos为0的时候直接进行yield操作,具体是Continuation.yield()
            tryYield();
        } else {
            // park for the sleep time
            try {
                long remainingNanos = nanos;
                // 临时变量记录开始休眠时间
                long startNanos = System.nanoTime();
                while (remainingNanos > 0) {
                    // 剩余休眠时间大于0纳秒,进行park操作
                    parkNanos(remainingNanos);
                    // 响应中断清理中断状态,抛出中断异常
                    if (getAndClearInterrupt()) {
                        throw new InterruptedException();
                    }
                    // 重新计算剩余休眠事件
                    remainingNanos = nanos - (System.nanoTime() - startNanos);
                }
            } finally {
                // park会消耗park许可,走到这里说明unpark了,可以重新设置许可
                setParkPermit(true);
            }
        }
    }

    // 当前虚拟线程park(阻塞)直至指定等候时间,进行unpark操作或者中断也能解除park状态
    @Override
    void parkNanos(long nanos) {
        assert Thread.currentThread() == this;

        // 已经消耗了park许可或者处于中断状态,直接返回
        if (getAndSetParkPermit(false) || interrupted)
            return;

        // 当前虚拟线程park(阻塞)直至指定等候时间
        if (nanos > 0) {
            // 记录开始park的时间
            long startTime = System.nanoTime();
            // 记录是否yield成功
            boolean yielded;
            // 通过调度线程池提交一个延时执行的unpark任务,用于进行unpark操作解除当前虚拟线程阻塞等待
            Future<?> unparker = scheduleUnpark(nanos);
            // 设置为PARKING状态
            setState(PARKING);
            try {
                // 执行Continuation.yield()
                yielded = yieldContinuation();
            } finally {
                assert (Thread.currentThread() == this)
                        && (state() == RUNNING || state() == PARKING);
                // 执行Continuation.yield()执行完毕后,如果该unparker任务未完成则进行取消操作
                cancel(unparker);
            }

            // Continuation.yield()调用失败,则重新计算等待时间并基于运载线程进行park操作
            if (!yielded) {
                long deadline = startTime + nanos;
                if (deadline < 0L)
                    deadline = Long.MAX_VALUE;
                parkOnCarrierThread(true, deadline - System.nanoTime());
            }
        }
    }

    // 当前虚拟线程的运载线程park(阻塞)直至指定等候时间,这就是前面提到过的pinned thread产生的过程
    private void parkOnCarrierThread(boolean timed, long nanos) {
        assert state() == PARKING;

        var pinnedEvent = new VirtualThreadPinnedEvent();
        pinnedEvent.begin();
        // 设置状态为PINNED
        setState(PINNED);
        try {
            // 如果没有park许可,则不处理,否则使用Usafe的park api进行平台线程阻塞
            if (!parkPermit) {
                if (!timed) {
                    U.park(false, 0);
                } else if (nanos > 0) {
                    U.park(false, nanos);
                }
            }
        } finally {
            // 阻塞解除后状态为RUNNING
            setState(RUNNING);
        }

        // 解除阻塞后此park操作消耗了park许可
        setParkPermit(false);

        pinnedEvent.commit();
    }

    @ChangesCurrentThread
    private Future<?> scheduleUnpark(long nanos) {
        Thread carrier = this.carrierThread;
        // need to switch to current platform thread to avoid nested parking
        carrier.setCurrentThread(carrier);
        try {
            return UNPARKER.schedule(() -> unpark(), nanos, NANOSECONDS);
        } finally {
            carrier.setCurrentThread(this);
        }
    }

    // 如果unpark任务未完成则取消它,这个过程需要切换到当前平台线程以避免嵌套park操作
    @ChangesCurrentThread
    private void cancel(Future<?> future) {
        if (!future.isDone()) {
            Thread carrier = this.carrierThread;
            // need to switch to current platform thread to avoid nested parking
            carrier.setCurrentThread(carrier);
            try {
                future.cancel(false);
            } finally {
                carrier.setCurrentThread(this);
            }
        }
    }

    // unpark操作,重新启用当前虚拟线程进行调度,如果虚拟线程处于park状态会将它解除阻塞
    @Override
    @ChangesCurrentThread
    void unpark() {
        Thread currentThread = Thread.currentThread();
        // 重置park许可false -> true,并且判断当前线程是虚拟线程
        if (!getAndSetParkPermit(true) && currentThread != this) {
            int s = state();
            // 命中虚拟线程PARKED状态,则CAS设置为RUNNABLE状态,并且重新提交Continuation的Runnable包装器到调度器中,
            // 这个提交过程需要切换到当前运载线程,然后恢复为当前虚拟线程
            if (s == PARKED && compareAndSetState(PARKED, RUNNABLE)) {
                if (currentThread instanceof VirtualThread vthread) {
                    Thread carrier = vthread.carrierThread;
                    carrier.setCurrentThread(carrier);
                    try {
                        submitRunContinuation();
                    } finally {
                        carrier.setCurrentThread(vthread);
                    }
                } else {
                    submitRunContinuation();
                }
            } else if (s == PINNED) {
                // park操作基于运载线程阻塞,则调用Usafe的unpark api进行唤醒,
                // 唤醒后在parkOnCarrierThread()中会重新被修改为RUNNING状态
                synchronized (carrierThreadAccessLock()) {
                    Thread carrier = carrierThread;
                    if (carrier != null && state() == PINNED) {
                        U.unpark(carrier);
                    }
                }
            }
        }
    }

    // 尝试执行Continuation.yield()
    void tryYield() {
        assert Thread.currentThread() == this;
        // 设置状态为YIELDING
        setState(YIELDING);
        try {
            // 执行Continuation.yield(),忽略返回值处理
            yieldContinuation();
        } finally {
            assert Thread.currentThread() == this;
            // 虚拟线程重新mount并且运行,设置为RUNNING状态
            if (state() != RUNNING) {
                assert state() == YIELDING;
                setState(RUNNING);
            }
        }
    }

    // 执行Continuation.yield()
    private boolean yieldContinuation() {
        boolean notifyJvmti = notifyJvmtiEvents;
        // 当前虚拟线程进行unmount操作
        if (notifyJvmti) notifyJvmtiUnmountBegin(false);
        unmount();
        try {
            // 执行Continuation.yield()
            return Continuation.yield(VTHREAD_SCOPE);
        } finally {
            // 当前虚拟线程重新进行mount操作
            mount();
            if (notifyJvmti) notifyJvmtiMountEnd(false);
        }
    }
}

总的来说就是:

  • 阻塞:通过Continuation.yield()调用实现阻塞,主要是提供给Thread.sleep()调用
  • 限时阻塞:Continuation.yield()调用之前计算唤醒时间并且向调度线程池(UNPARKER)提交一个「延时执行」的unpark任务通过"懒提交"方式重新运行Continuation.run()调用链解除阻塞,主要是提供给Thread.sleep(long nanos)调用
  • join(Nanos):通过CountDownLatch.await()调用实现阻塞,在虚拟线程终结钩子方法afterTerminate()中调用CountDownLatch.countDown()解除阻塞,join(Nanos)()方法主要是提供给Thread.join()调用
  • 特殊情况:如果Continuation.yield()调用失败,则会通过Unsafe提供的park API阻塞在运载线程上,在unpark任务中通过Unsafe提供的unpark API解除阻塞

分析完虚拟线程实现的核心代码,这里总结一下虚拟线程的状态切换,由于支持的状态比较多,这里通过一张状态图进行展示:

还有其他像获取虚拟线程栈、JVM状态通知、获取虚拟线程状态、状态切换的CAS操作等方法限于篇幅这里就不展开分析。

3.3 线程建造器

线程建造器和线程工厂建造器用于快速创建平台线程实例、平台线程工厂实例、虚拟线程实例或者虚拟线程工厂实例。熟悉Builder模式的开发者看这个新引入的功能源码应该比较轻松:

// 内部类:java.lang.Thread.Builder
// Builder只对OfPlatform、OfVirtual、BaseThreadBuilder开放继承权限
@PreviewFeature(feature = PreviewFeature.Feature.VIRTUAL_THREADS)
public sealed interface Builder
        permits Builder.OfPlatform,
                Builder.OfVirtual,
                ThreadBuilders.BaseThreadBuilder {
 
    // 设置线程名称
    Builder name(String name);
    
    // 设置线程名称规则,最终线程名称为:$prefix$start++
    // 如prefix: worker-, start: 0,则worker-0, worker-1.... worker-n
    Builder name(String prefix, long start);
 
    Builder allowSetThreadLocals(boolean allow);
 
    // 是否开启InheritableThreadLocal
    Builder inheritInheritableThreadLocals(boolean inherit);
 
    // 设置未捕获异常处理器
    Builder uncaughtExceptionHandler(UncaughtExceptionHandler ueh);
 
    // 设置非启动前的任务实例
    Thread unstarted(Runnable task);
 
    // 设置任务实例并且马上启动
    Thread start(Runnable task);
 
    // 构建线程工厂实例
    ThreadFactory factory();
 
    // 平台线程Builder接口
    @PreviewFeature(feature = PreviewFeature.Feature.VIRTUAL_THREADS)
    sealed interface OfPlatform extends Builder
            permits ThreadBuilders.PlatformThreadBuilder {
 
        @Override 
        OfPlatform name(String name);
        
        @Override 
        OfPlatform name(String prefix, long start);
        
        @Override 
        OfPlatform allowSetThreadLocals(boolean allow);
        
        @Override 
        OfPlatform inheritInheritableThreadLocals(boolean inherit);
        
        @Override 
        OfPlatform uncaughtExceptionHandler(UncaughtExceptionHandler ueh);
 
        // 设置平台线程组
        OfPlatform group(ThreadGroup group);
 
        // 设置新建平台线程是否为守护线程
        OfPlatform daemon(boolean on);
 
        // 判断新建平台线程是否为守护线程
        default OfPlatform daemon() {
            return daemon(true);
        }
 
        // 设置优先级
        OfPlatform priority(int priority);
 
        // 设置线程栈大小
        OfPlatform stackSize(long stackSize);
    }
 
    // 虚拟线程Builder接口
    @PreviewFeature(feature = PreviewFeature.Feature.VIRTUAL_THREADS)
    sealed interface OfVirtual extends Builder
            permits ThreadBuilders.VirtualThreadBuilder {
 
        @Override 
        OfVirtual name(String name);
        
        @Override 
        OfVirtual name(String prefix, long start);
        
        @Override 
        OfVirtual allowSetThreadLocals(boolean allow);
        
        @Override 
        OfVirtual inheritInheritableThreadLocals(boolean inherit);
        
        @Override 
        OfVirtual uncaughtExceptionHandler(UncaughtExceptionHandler ueh);
    }
}

上面的Builder接口都在java.lang.ThreadBuilders中进行实现,因为整体实现比较简单,这里只看全新引入的VirtualThreadFactoryVirtualThreadBuilder

// 内部类:java.lang.ThreadBuilders.VirtualThreadFactory
private static class VirtualThreadFactory extends BaseThreadFactory {

    // 执行器或者说调度器实例
    private final Executor scheduler;

    // 线程工厂构造函数基本与平台线程工厂实现一致,但是必须提供执行器实例
    VirtualThreadFactory(Executor scheduler,
                         String name,
                         long start,
                         int characteristics,
                         UncaughtExceptionHandler uhe) {
        super(name, start, characteristics, uhe);
        this.scheduler = scheduler;
    }

    @Override
    public Thread newThread(Runnable task) {
        Objects.requireNonNull(task);
        // 获取下一个虚拟线程名称,start >= 0则为$name$start++,否则固定为name
        String name = nextThreadName();
        // 创建新的虚拟线程实例
        Thread thread = newVirtualThread(scheduler, name, characteristics(), task);
        UncaughtExceptionHandler uhe = uncaughtExceptionHandler();
        if (uhe != null)
            // 设置未捕获异常处理器
            thread.uncaughtExceptionHandler(uhe);
        return thread;
    }
}

// 静态方法:java.lang.ThreadBuilders#newVirtualThread()
static Thread newVirtualThread(Executor scheduler,
                               String name,
                               int characteristics,
                               Runnable task) {
    // 当前JVM支持Continuation,则创建初始化一个新的虚拟线程实例
    if (ContinuationSupport.isSupported()) {
        return new VirtualThread(scheduler, name, characteristics, task);
    } else {
        // 当前的JVM不支持Continuation,则虚拟线程退化为一个平台线程的包装类,要求执行器必须为空
        if (scheduler != null)
            throw new UnsupportedOperationException();
        return new BoundVirtualThread(name, characteristics, task);
    }
}

// 内部类:java.lang.ThreadBuilders.VirtualThreadBuilder
static final class VirtualThreadBuilder
        extends BaseThreadBuilder<OfVirtual> implements OfVirtual {

    // 执行器成员变量
    private Executor scheduler;

    VirtualThreadBuilder() {
    }

    // 目前VirtualThreadBuilder的构造都是默认修饰符,Executor只能在单元测试中调用
    // 也就是用户无法设置Executor,因为所有虚拟线程默认都是由全局的ForkJoinPool调度
    // invoked by tests
    VirtualThreadBuilder(Executor scheduler) {
        if (!ContinuationSupport.isSupported())
            throw new UnsupportedOperationException();
        this.scheduler = Objects.requireNonNull(scheduler);
    }

    // 创建虚拟线程实例,设置任务,处于非启动状态
    @Override
    public Thread unstarted(Runnable task) {
        Objects.requireNonNull(task);
        var thread = newVirtualThread(scheduler, nextThreadName(), characteristics(), task);
        UncaughtExceptionHandler uhe = uncaughtExceptionHandler();
        if (uhe != null)
            thread.uncaughtExceptionHandler(uhe);
        return thread;
    }

    // 创建虚拟线程实例,设置任务并且马上启动
    @Override
    public Thread start(Runnable task) {
        Thread thread = unstarted(task);
        thread.start();
        return thread;
    }

    // 初始化虚拟线程工厂实例
    @Override
    public ThreadFactory factory() {
        return new VirtualThreadFactory(scheduler, name(), counter(), characteristics(),
                uncaughtExceptionHandler());
    }
}

值得注意的是:虚拟线程实现上来看都是"守护线程",也就是说虚拟线程不需要设置daemon参数。平台线程或者虚拟线程的建造器或者工厂实现都是包访问权限的内部类,其父类使用了permits关键字指定继承范围,目前是只能通过链式设置值的方式初始化,无法修改其中的成员或者方法

四、虚拟线程的使用

4.1 创建虚拟线程的方式

java中创建的虚拟线程本质都是通过Thread.Builder.OfVirtual对象进行创建的,下面先看下创建虚拟线程的三种方式:

  1. 通过Thread.startVirtualThread直接创建一个虚拟线程
public class VirtualThreadTest {
    public static void main(String[] args) {
        //创建任务
        Runnable task = () -> {
            System.out.println("执行任务");
        };

        //创建虚拟线程将任务task传入并启动
        Thread thread = Thread.startVirtualThread(task);

        //等待线程执行完毕
        thread.join();
    }
}
  1. 使用Thread.ofVirtual()方法创建
public class VirtualThreadTest {
    public static void main(String[] args) {
        //创建任务
        Runnable task = () -> {
            System.out.println(Thread.currentThread().getName());
        };

        //创建虚拟线程命名为诺手,将任务task传入
        Thread thread = Thread.ofVirtual().name("诺手").unstarted(task);
        thread.start();//启动虚拟线程

        //等待线程执行完毕
        thread.join();
    }
}

也可以在创建虚拟线程的时候直接启动

public class VirtualThreadTest {
    public static void main(String[] args) {
        //创建任务
        Runnable task = () -> {
            System.out.println(Thread.currentThread().getName());
        };

        //创建虚拟线程命名为诺手,将任务task传入并启动
        Thread thread = Thread.ofVirtual().name("诺手").start(task);

        //等待线程执行完毕
        thread.join();
    }
}
  1. 通过ExecutorService创建,为每个任务分配一个虚拟线程,下面代码中提交了100个任务,对应会有100个虚拟线程进行处理。
public class VirtualThreadTest {
    public static void main(String[] args) {
        /*
         * 通过ExecutorService创建虚拟线程
         * ExecutorService实现了AutoCloseable接口,可以自动关闭了
         */
        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
            //向executor中提交100个任务
            IntStream.range(0, 100).forEach(i -> {
                executor.submit(() -> {
                    //睡眠1秒
                    try {
                        Thread.sleep(Duration.ofSeconds(1));
                        System.out.println(i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            });
        }
    }
}

现在平台线程和虚拟线程都是Thread的对象,那该如何区分该对象是平台线程还是虚拟线程?可以利用Thread中的isVirtual()方法进行判断,返回true表示虚拟线程:

public class VirtualThreadTest {
    public static void main(String[] args) {
        //创建任务
        Runnable task = () -> {
            System.out.println("执行任务");
        };

        //创建虚拟线程将任务task传入并启动
        Thread vt = Thread.startVirtualThread(task);
        System.out.println(vt.isVirtual());
    }
}

4.2 Thread.builder接口

jdk19中新增了一个密封(sealed)接口Builder,该接口只允许有两个子接口:

  • ofPlatform:创建平台线程的时候使用,是一个密封接口,只允许ThreadBuilders.PlatformThreadBuilder实现。
  • ofVirtual:创建虚拟线程的时候使用,是一个密封接口,只允许ThreadBuilders.VirtualThreadBuilder实现。

上面3种创建虚拟线程的方式本质都是通过OfVirtual来进行创建的,OfVirtualOfPlatform接口中的api很多是相同的,OfPlatform中的方法更多,所以下面我们以OfPlatform为例演示他的使用方式。

通过ofPlatform中的factory()方法可以创建一个ThreadFactory线程工厂,它可以帮助我们创建出平台线程对象。

public class Test5 {
    public static void main(String[] args) {
        //1. 通过OfPlatform创建平台线程对象
        ThreadFactory threadFactory = Thread.ofVirtual().factory();

        //2. 创建任务
        Runnable task = () -> {
            if (Thread.currentThread().isVirtual()) {
                System.out.println("当前线程是虚拟线程");
            } else {
                System.out.println("当前线程是平台线程");
            }
        };
        Thread thread = threadFactory.newThread(task);
        thread.start();
        thread.join();
    }
}

上面创建平台线程的方式跟之前的new Thread是一样的,优点是我们可以用它来实现链式编程,比如要设置线程优先级,线程名字,守护线程:

public class Test5 {
    public static void main(String[] args) {
        //创建任务
        Runnable task = () -> {
            System.out.println(Thread.currentThread().getName());
        };

        //链式编程
        Thread.ofVirtual().name("小").start(task);
    }
}

4.3 CompletableFuture的虚拟线程

public class Test6 {
    public static void main(String[] args) {
        // 创建虚拟线程执行器
        Executor executor = Executors.newVirtualThreadPerTaskExecutor();

        // 异步任务示例
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            if (Thread.currentThread().isVirtual()) {
                System.out.println("当前线程是虚拟线程");
            } else {
                System.out.println("当前线程是平台线程");
            }
            System.out.println("开始任务:" + Thread.currentThread().getName());
            try {
                Thread.sleep(1000); // 模拟耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("任务完成!");
        }, executor);

        // 等待任务完成
        future.join();
        System.out.println("所有任务完成!");
    }
}

此示例展示了CompletableFuture中虚拟线程的用法。我们使用supplyAsync()thenApplyAsync()thenAcceptAsync()方法链接异步任务。这些任务在虚拟线程中执行,从而实现高效的异步处理。

五、总结

虚拟线程是由Java运行时而不是操作系统实现的Java线程。虚拟线程和传统线程(我们现在称之为平台线程)之间的主要区别在于,我们可以很容易地在同一个Java进程中运行大量活动的虚拟线程,甚至数百万个。大量的虚拟线程赋予了它们强大的功能:通过允许服务器并发处理更多的请求,它们可以更有效地运行以每个请求一个线程的方式编写的服务器应用程序,从而实现更高的吞吐量和更少的硬件浪费。

六、拓展

6.1 避免使用线程池与虚拟线程结合

public class Test {
    public static void main(String[] args) {
        // × 不推荐的做法
        ExecutorService platformExecutor = Executors.newFixedThreadPool(100);
        ExecutorService virtualExecutor = Executors.newVirtualThreadPerTaskExecutor();
        virtualExecutor.submit(() -> {
            // 在虚拟线程中使用平台线程池
            platformExecutor.submit(() -> {
                // 这里的任务会被阻塞在有限的平台线程池中
                doSomeWork();
            });
        });

        // √ 推荐的做法
        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            IntStream.range(0, 10_000).forEach(i -> {
                executor.submit(() -> {
                    doSomeWork();
                    return i;
                });
            });
        }
    }
}
  • 线程池的设计初衷是复用线程,因为传统线程的创建和销毁开销很大,但是虚拟线程的创建和销毁开销很小,所以自然没必要进行池化。
  • 使用线程池反而会限制并发数,限制了虚拟线程轻量级、高并发的优势。
  • 可能导致线程饥饿或死锁

6.2 避免使用ThreadLocal

public class Test {
    // × 不推荐的做法
    private static ThreadLocal<User> userContext = new ThreadLocal<>();
    public void processUser(User user) {
        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            executor.submit(() -> {
                userContext.set(user);  // 在虚拟线程中使用 ThreadLocal
                doSomeWork();
                userContext.remove();
            });
        }
    }
    
    // √ 推荐的做法 使用ScopedValue
    // 创建一个ScopedValue实例来存储User上下文
    private static final ScopedValue<User> userContext2 = ScopedValue.newInstance();
    public void processUser2(User user) {
        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            executor.submit(() -> {
                // 使用where()方法绑定值,并在run()中执行业务逻辑
                ScopedValue.where(userContext2, user)
                        .run(() -> {
                            // 在这里可以通过get()方法获取上下文中的user对象
                            User currentUser = userContext2.get();
                            System.out.println("Current user: " + currentUser);
                            doSomeWork();
                        });
            });
        }
    }
}
  • 虚拟线程会频繁切换载体线程,ThreadLocal的值可能会丢失或混乱,建议使用ScopedValue
  • 会导致内存泄漏,因为虚拟线程数量可能非常大。

6.3 避免使用同步块或重量级锁(jdk25版本有优化)

public class Test {
    // x 不推荐的做法
    private final Object lock = new Object();
    private int counter = 0;
    public void increment() {
        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            executor.submit(() -> {
                synchronized (lock) {  // 使用同步块会阻塞载体线程
                    counter++;
                }
            });
        }
    }

    // √ 推荐的做法
    private final AtomicInteger counter2 = new AtomicInteger(0);
    public void increment2() {
        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            executor.submit(() -> {
                counter2.incrementAndGet();  // 使用无锁数据结构
            });
        }
    }
}
  • 同步块会导致载体线程被阻塞,降低性能。
  • 可能导致其他虚拟线程无法执行
  • 应该使用无锁数据结构或更轻量级的并发控制机制

6.4 避免固定虚拟线程到载体线程

public class Test {
    // x 不推荐的做法
    public void doWork() {
        Thread vThread = Thread.ofVirtual().start(() -> {
            // 这些操作会导致虚拟线程被固定到载体线程
            synchronized (this) {
                heavyComputation();
            }
        });
    }

    // √ 推荐的做法
    public void doWork2() {
        Thread vThread = Thread.ofVirtual().start(() -> {
            // 使用非阻塞操作或轻量级同步机制
            CompletableFuture.runAsync(this::heavyComputation);
        });
    }
}
  • 固定(pinning)会导致虚拟线程失去其轻量级特性
  • 会降低整体性能和可伸缩性
  • 应该尽量使用非阻塞操作或异步方式

6.5 注意内存使用

虽然单个虚拟线程很轻量(约 2KB),但创建大量虚拟线程仍然需要注意内存使用:

// x 可能导致内存问题的代码
public class Test {
    public static void createManyThreads() {
        List<Thread> threads = new ArrayList<>();
        for (int i = 0; i < 1_000_000; i++) {
            Thread vt = Thread.ofVirtual().start(() -> {
                // 每个线程持有大量数据
                byte[] data = new byte[1024 * 1024]; // 1MB
                try {
                    Thread.sleep(Duration.ofHours(1));
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
            threads.add(vt);
        }
    }
}
  • 控制并发虚拟线程的数量
  • 注意每个虚拟线程中的内存使用
  • 及时释放不需要的资源
posted @ 2025-05-05 22:04  夏尔_717  阅读(642)  评论(0)    收藏  举报