因为在Java19之前,Java只有一种线程,然后为了与19中的虚拟线程区分开来,所以之前的线程统称为平台线程。

平台线程与操作系统的内核线程是一一对应的。

然后我们主要来谈论19中新添加(模仿)的线程–虚拟线程

虚拟线程特征

  • 是用户模式线程
  • 由Java运行时调度
  • 虚拟线程和内核线程是M对N的对应关系,也就是M个虚拟线程会被映射到N个内核线程上

虚拟线程可以使用独占线程处理每个请求的并发风格,也就是thread-per-request

thread-per-request特点

  • 使用独占的线程来处理该请求
  • 易于理解和编程实现
  • 易于调优和性能调优

但是thread-per-request不能用平台线程来实现,因为平台线程是操作系统中的线程操作的一种封装,而操作系统的线程会占用资源,存在数量上限,对于一个海量级别的服务端来说,不可能一个请求来,然后去生成一个平台线程。

为了实现thread-per-request,目前有这几种解决思路

  • 依赖于非阻塞I/O和异步编程(用少量线程处理大量的请求)
  • 可以提升系统的吞吐量
  • 开发人员必须熟悉所使用的底层框架

使用虚拟线程好处

  • 使用最自然的方式来编写代码
  • 把请求的处理逻辑全部在一个虚拟线程中完成
  • 降低了编写高并发服务端应用的难度

虚拟线程不需要放入线程池

虚拟线程的调度

  • 由JDK负责调度
  • JDK把虚拟线程分配个平台线程
  • 平台线程则由操作系统负责调度

一个虚拟线程所分配的平台线程被称为该虚拟线程的载体,然后一个虚拟线程可能被调度到多个载体上,载体的标识对于虚拟线程是不可见的

JDK调度虚拟线程时,使用的是一个以FIFO模式工作的work-stealing ForkJoinPool,该ForkJoinPool的paralleism决定了调度时可以使用的平台线程的数量

虚拟线程的执行

  • 把虚拟线程绑定到平台线程

  • 从平台线程上接触绑定,当虚拟线程在等待I/O或是执行某些阻塞操作时,可以从平台线程上解除绑定

    等待阻塞操作完成之后,可以绑定到新的平台线程上继续执行

  • 对于应用代码来说是透明的

有些JDK中的阻塞操作并不会解除对平台线程的绑定,因此会阻塞平台线程和底层的操作系统线程,比如

文件操作、Object.wait()方法调用,这些阻塞操作的实现会在内部对此进行补偿

临时增加JDK的调度器可以使用的线程数量

在下面两种情况下,虚拟线程会被Pin在载体上而无法解除绑定

  • 在执行Synchronized方法或块时
  • 在执行native方法或外部方法时

创建虚拟线程

第一种

1
var thread = Thread.ofVirtual().name("my virtual").start(() -> System.out.println("运行"));

一个新的虚拟线程被创建并启动,返回的时java.lang.Thread类的对象

第二种

1
Thread.startVirtualThread(Runnable task)

第三种

使用线程工厂来实现

1
2
var factory = Thread.ofVirtual().factory();
var thread = factory.newThread(()-> System.out.println("在工厂中创建"));

第四种

使用线程池

1
2
Executors.newVirtualThreadPerTaskExecutor();
Executors.newThreadPerTaskExecutor(ThreadFactory threadFactory);

用Executors对象所能创建的线程数量理论上没有上限(受限于内存)