因为在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对象所能创建的线程数量理论上没有上限(受限于内存)