Java 19 最核心的特性就是虚拟线程(Virtual Threads)

简介

该特性在Java19中是预览特性,虚拟线程是用户态下的线程,和go语言中的goroutinesErlang中的processes类似,虚拟线程并非比线程快,而是提高了应用的吞吐量,相比于传统的线程是由操作系统调度来看,虚拟线程是我们自己程序调度的线程。虚拟线程的出现并没有修改Java原有的并发模型,也不会替代原有的线程,虚拟线程主要作用是提高服务器端的吞吐量(主要解决IO密集型而非CPU密集型任务)

吞吐量的瓶颈

服务器应用程序的伸缩性受利特尔法则(Little's Law)的制约,与下面三点有关:

  • 延迟:请求处理的耗时
  • 并发量:同一时刻处理的请求数量
  • 吞吐量:单位时间内处理的数据数量

如果一个服务处理延迟是50ms,处理10个并发请求,则吞吐量是200请求/秒(10/0.05),如果吞吐量要达到2000请求/秒,则处理的并发请求数量是100. 如果按照1个请求一个线程来看,要想提高吞吐量,线程数量也要增加。

Java中的线程在操作系统线程(OS Thread)进行了一层封装,而操作系统重线程是重量级资源,在硬件配置确定的前提下,不能无限制创建线程。

os thread
virtual thread

与虚拟地址可以映射到物理内存类似,Java将大量的虚拟线程映射到少量的操作系统线程上,虚拟线程的生命周期短暂,不会有很深的栈调用,一个虚拟线程的生命周期只运行一个任务,因此可以大量创建虚拟线程,且无需池化

虚拟线程的应用场景

在服务器端的应用程序中,虚拟线程能够明显提高应用的吞吐量:

  • 至少几千的并发任务量
  • 任务是IO密集型

平台线程和虚拟线程

平台线程(platform thread):指Java中的线程,比如通过new Thread()创建的线程。
虚拟线程并不会直接分配给CPU执行,而是通过调度器分配给平台线程,平台线程再被调度器管理。Java中的虚拟线程的调度器采用了工作窃取的模式进行FIFO的操作,调度器的并行书默认是jvm获取的处理器数量(Runtime.getRuntime().availableProcessors()),调度器并非分时(time sharing)的。在使用虚拟线程编写程序时,不能控制虚拟线程合适分配给平台线程,也不能控制平台线程合适分配给CPU。

以前任务和平台线程的关系:
old mode
在使用虚拟线程之后,任务-虚拟线程-调度器-平台线程的关系,1个平台线程可以被调度器分配不同的虚拟线程:
new mode

携带器

调度器将虚拟线程挂载到平台线程之后,该平台线程叫做虚拟线程的携带器,调度器并不维护虚拟线程和携带器的关联关系,因此在一个虚拟线程的生命周期可以被分配到不同的携带器,即虚拟线程运行了一小段代码后,可能会脱离携带器,此时其他的虚拟线程会被分配到这个携带器上。

携带器和虚拟线程是相互独立的,比如:

  • 虚拟线程不能使用携带器的标识,Thread.current()方法获取的是虚拟线程本身。
  • 两者有各自的栈空间。
  • 两者不能访问对方的ThreadLocal变量

在程序的执行过程中,虚拟线程遇到阻塞的操作是大部分情况下会被解除挂载,阻塞结束后,虚拟线程会被调度器重新挂载到携带器上,因此虚拟线程会频繁的挂载和解除挂载,这并不会导致操作系统线程的阻塞。
有些阻塞操作并不会导致虚拟线程接触挂载,这样会同时阻塞携带器和操作系统线程,例如:操作系统基本的文件操作,Java的Object.wait()方法,下面两种情况下不会导致虚拟线程的解除挂载:

  • 执行synchronized同步代码(会导致携带器阻塞,所以建议使用ReentrantLock替换掉Synchronized
  • 执行本地方法或外部函数

虚拟线程和平台线程api的区别

从内存空间上来说,虚拟线程的栈空间可以看做是一个大块的站对象,他被存储在Java堆中,相比于单独存储对象,堆中存储虚拟线程会造成一些空间的浪费,这点在后续的Java版本中应该会得到改善,当然也有一些好处,就是可以重复利用这部分栈空间,不用多次申请开辟新的内存地址,虚拟线程的栈空间最大可以达到平台线程的栈空间容量。
虚拟线程并不是GC root,其中的引用不会出现Stop World,当虚拟线程阻塞之后比如BlockingQueue.take(),平台线程既不能获取到虚拟线程,也不能获取到queue队列,这样该平台线程可能被回收掉,虚拟线程在运行或阻塞时不会被GC

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

创建虚拟线程的方式

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

  • Thread.startVirtualThread()直接创建一个虚拟线程
  • Thread.ofVirtual().name("virtual-thread-").unstarted(r)
  • ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()

示例代码

虚拟线程池

import java.time.Duration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;

/**
 * @author imyzt
 * @date 2023/12/18
 * @description VirtualThread 1
 */
public class VirtualThread {

    public static void main(String[] args) throws InterruptedException {

        // 通过线程池创建虚拟线程池
        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
            IntStream.range(0, 10000000).forEach(i -> {
                executor.submit(() -> {
                    try {
                        Thread.sleep(Duration.ofSeconds(1));
                        System.out.println("执行任务: " + i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

创建虚拟线程

import java.util.concurrent.TimeUnit;

/**
 * @author imyzt
 * @date 2023/12/18
 * @description VirtualThread 2
 */
public class CreateVirtualThread {
    public static void main(String[] args) throws InterruptedException {

        Runnable r = () -> System.out.println(Thread.currentThread().getName() + " --- 执行了");

        // 创建虚拟线程, 方式1
        Thread.startVirtualThread(r);

        Thread virtualThread = Thread.ofVirtual().name("virtual-thread-").unstarted(r);
        virtualThread.start();
        System.out.println("是虚拟线程吗? " + virtualThread.isVirtual());

        Thread platformThread = Thread.ofPlatform().priority(0).daemon(true).name("platform-thread-").unstarted(r);
        platformThread.start();
        System.out.println("是虚拟线程吗? " + platformThread.isVirtual());

        // --- 执行了
        //virtual-thread- --- 执行了
        //是虚拟线程吗? true
        //platform-thread- --- 执行了
        //是虚拟线程吗? false

        // 主线程休眠
        TimeUnit.SECONDS.sleep(1);
    }
}

其它

除了提出虚拟线程外,还提出来新的并发编程模型结构化并发