Java21新特性-虚拟线程
虚拟线程是轻量级线程(类似于 Go 中的 “协程(Goroutine)”),可以减少编写、维护和调度高吞吐量并发应用程序的工作量。
线程是可供调度的最小处理单元,它与其他类似的处理单元并发运行,并且在很大程度上是独立运行的。线程(java.lang.Thread
)有两种,平台线程和虚拟线程。
一. Java内部线程实现模式
绿色线程(Green Thread):远古时期,Java使用绿色线程模式。这个模式下,多线程的调度和管理有JVM完成。绿色线程模式才作用M:1线程映射模型。这里就有一个问题,Java不能够规模化管理这种线程,也就无法充分发挥硬件性能。同样的实现绿色线程也是一件非常有挑战性的事情,因为它需要非常底层的支持才能够良好运行。随后Java移除了绿色线程,转而使用本地线程。这使得Java的线程执行比绿色线程更慢。
平台线程(Platform Thread):从Java 1.2开始从绿色线程切换到了平台线程模式(有些人称之为本地线程(Native Thread))。在操作系统的帮助下,JVM 得以控制平台线程。平台线程的执行效率很高,但是开启和关闭他们的资源消耗较大。这就是为什么我们现在要使用线程池。这个模型遵循着 1:1 线程映射,即一个Java线程映射到一个内核线程。当一个Java线程被创建时,相应的一个对应的核心线程也会被创建,用来执行线程代码。自此之后,平台线程模型的做法就延续到了今天。
1.1 当前Java线程模型有什么问题吗?
- 只是对于操作系统内核线程的一个简单包装,真正的线程调度,还是由操作系统完成;
- 因为线程的创建和销毁都需要系统内核完成,涉及用户态切换,资源消耗较大;
- 本地线程需要保存他们的调用栈在内存中,大概2MB~20MB的预留空间。如果你有4GB内存,如果每个线程占用20MB内存,那么你就只能创建大概200个线程;
- 因为本地线程是一种系统资源,加载一个新的本地线程大概需要1毫秒;
- 上下文切换代价昂贵,需要一个到内核的系统调用;
- 上面这些强制性的限制会限制线程创建的数量,同时会导致性能下降和过度的内存消耗。因为我们不能创建更多的线程;
- 我们不能通过增加更多的线程来增应用规模,因为上下文切换和内存占用的代价高昂;
1.2 一个IO密集型应用的例子
考虑一台16GB内存的网络服务器。对于每个服务请求,都分配一个不同的线程。我们假设每个线程需要20MB内存空间,那么这台机器可以支持800个线程。当前,后端的API一般使用REST/SOAP调用方式,例如数据库操作和API信息转发这些IO密集型操作。由此可见,后端服务的主要是IO密集型而不是CPU密集型。
接着假设一下,一个IO操作需要100毫秒,请求执行(IO密集型)需要100毫秒,以及返回结果也需要100毫秒。同时,当每秒有800个请求时,线程数得到了最大容量。
让我们来计算一下单个请求的CPU占用时间
CPU时间 = 请求准备时间 + 返回结果准备时间
= 0.1ms + 0.1ms
= 0.2ms
对于800个请求呢?
800个线程的请求时间= 800 * 0.2ms
= 160ms
受限于我们的内存容量,我们只能创建800个请求,也就导致了我们CPU使用率并不高
CPU使用率=160ms / 1000ms
= 16%
那么如何才能使CPU的利用率到达90%呢?
16% = 800个线程
90% = X个线程
X = 4500
但是我们当前因为内存的限制不能创建那么多的线程,除非我们能突破这个限制,拥有90G内存。
90G的内存是一个比较离谱的数字,所以说创建本地线程很明显不能充分利用硬件资源。
二. 虚拟线程
虚拟线程是一个Java线程的轻量级实现版本,最早于JDK19中出现,当前仍是预览状态,可以通过Jvm配置项开启。
虚拟线程是JVM项目loom的一部分
虚拟线程解决了传递和维护本地线程的瓶颈问题,同时可以用之编写高吞吐的并发应用,榨干硬件资源的潜力。
与本地线程不同,虚拟线程并不有操作系统控制,虚拟线程是一个有JVM管理的用户态线程。对比于本地线程的高资源占用,每个虚拟线程只需要几个字节的内存空间。这是的它更适合控制管理大量的用户访问,或者说处理IO密集型任务。
在创建虚拟线程的数量上几乎没有限制,甚至可以创建一百万个,因为虚拟线程并不需要来自内核的系统调用。
在虚拟线程如此轻量化的条件下,线程池不再成为必须品,只需要在需要的时候尽情创建虚拟线程就好。
虚拟线程和传统的本地线程操作完全兼容,例如本地线程变量,同步块,线程中断,等等。
2.1 虚拟线程如何工作
虚拟线程是一种轻量级(用户模式)线程,这种线程是由Java
虚拟机调度,而不是操作系统。虚拟线程占用空间小,任务切换开销几乎可以忽略不计,因此可以极大量地创建和使用。总体来看,虚拟线程实现如下:
virtual thread = continuation + scheduler
虚拟线程会把任务(一般是java.lang.Runnable
)包装到一个Continuation
实例中:
- 当任务需要阻塞挂起的时候,会调用
Continuation
的yield
操作进行阻塞 - 当任务需要解除阻塞继续执行的时候,
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运行时会挂起虚拟线程,然后切换到另一个可执行的虚拟线程,直到它可以恢复为止。
三. 虚拟线程的使用
官方提供了以下四种方式创建虚拟线程:
- 使用
Thread.startVirtualThread()
创建 - 使用
Thread.ofVirtual()
创建 - 使用
ThreadFactory
创建
3.1 使用 Thread.startVirtualThread() 创建
public class VirtualThreadTest {
public static void main(String[] args) {
CustomThread customThread = new CustomThread();
Thread.startVirtualThread(customThread);
}
}
static class CustomThread implements Runnable {
@Override
public void run() {
System.out.println("CustomThread run");
}
}
3.2 使用 Thread.ofVirtual()创建
public class VirtualThreadTest {
public static void main(String[] args) {
CustomThread customThread = new CustomThread();
// 创建不启动
Thread unStarted = Thread.ofVirtual().unstarted(customThread);
unStarted.start();
// 创建直接启动
Thread.ofVirtual().start(customThread);
}
}
static class CustomThread implements Runnable {
@Override
public void run() {
System.out.println("CustomThread run");
}
}
3.3 使用 ThreadFactory 创建
public class VirtualThreadTest {
public static void main(String[] args) {
CustomThread customThread = new CustomThread();
ThreadFactory factory = Thread.ofVirtual().factory();
Thread thread = factory.newThread(customThread);
thread.start();
}
}
static class CustomThread implements Runnable {
@Override
public void run() {
System.out.println("CustomThread run");
}
}
Java虚拟线程 - 昕希 - 博客园 (cnblogs.com)
Java21手册(一):虚拟线程 Virtual Threads - 掘金 (juejin.cn)
Java 21 正式 GA,虚拟线程真的来了 - calvinit - 博客园 (cnblogs.com)
Java21新特性-虚拟线程的更多相关文章
- C++11新特性之线程操作
C++11之前没有对并发编程提供语言级别的支持,这使得我们在编写可移植的并发程序时,存在诸多的不便.现在C++11增加了线程以及线程相关的类,很方便地支持了并发编程,使得编写的多线程程序的可移植性得到 ...
- JDK5新特性之线程同步工具类(三)
一. Semaphore Semaphore能够控制同一时候訪问资源的线程个数, 比如: 实现一个文件同意的并发訪问数. Semaphore实现的功能就类似厕全部5个坑, 增加有十个人要上厕所, 那么 ...
- Oracle 11g新特性虚拟列分区
如今有个需求:一个单据表要依照月份来分区.假设是在Oracle 10g上,仅仅能再加一个字段. 在Oracle 11g以后就不一样了.能够用虚拟列处理. SQL> select * from v ...
- JDK5新特性之线程同步集合(五)
一. 传统集合: 传统方式下的Collection在迭代集合时, 不同意对集合进行改动: public class CollectionModifyExceptionTest { public sta ...
- Java的虚拟线程(协程)特性开启预览阶段,多线程开发的难度将大大降低
高并发.多线程一直是Java编程中的难点,也是面试题中的要点.Java开发者也一直在尝试使用多线程来解决应用服务器的并发问题.但是多线程并不容易,为此一个新的技术出现了,这就是虚拟线程. 传统多线程的 ...
- 虚拟线程 - VirtualThread源码透视
前提 JDK19于2022-09-20发布GA版本,该版本提供了虚拟线程的预览功能.下载JDK19之后翻看了一下有关虚拟线程的一些源码,跟早些时候的Loom项目构建版本基本并没有很大出入,也跟第三方J ...
- Java19虚拟线程都来了,我正在写的线程代码会被淘汰掉吗?
Java19中引入了虚拟线程,虽然默认是关闭的,但是可以以Preview模式启用,这绝对是一个重大的更新,今天Java架构杂谈带大家开箱验货,看看这家伙实现了什么了不起的功能. 1 为什么需要虚拟线程 ...
- 转:c++ 11 新特性
声 明:本文源自 Danny Kalev 在 2011 年 6 月 21 日发表的<The Biggest Changes in C++11(and Why You Should Care)&g ...
- Java SE 19 虚拟线程
Java SE 19 虚拟线程 作者:Grey 原文地址: 博客园:Java SE 19 虚拟线程 CSDN:Java SE 19 虚拟线程 说明 虚拟线程(Virtual Threads)是在Pro ...
- Oracle 11g 新特性(一)-- 虚拟列
数据库版本: Oracle Database 11g Enterprise Edition Release 11.2.0.2.0 - 64bit Oracle11g 增加了虚拟列的新特性, 具体说明如 ...
随机推荐
- centos部署Django一:环境搭建
前言: 参考文档: https://www.cnblogs.com/djangocn/p/9538551.html https://www.icode9.com/content-3-546765.ht ...
- win7电脑IE浏览器开发人员工具中不能切换浏览器版本
win7电脑 IE浏览器 开发人员工具,不能切换IE版本 这个是IE浏览器的问题,需要安装个微软件东西就可以解决这个问题了.亲测有效 64位下载地址:https://wwi.lanzoui.com/i ...
- 报表如何批量导出成 excel 文件
需求说明 报表展现后可以通过工具栏中的导出按钮将当前展现的报表导出成 excel 文件,但是在实际使用中通常会要求报表不需要展现,直接通过一些操作将报表导出成 excel 文件,并且往往会要求批量导出 ...
- flask售后评分系统
做软件行业的公司,一般都有专业的售前售后团队,还有客服团队,客服处理用户反馈的问题,会形成工单,然后工单会有一大堆工单流程,涉及工单的内部人员,可能会有赔付啥的,当然,这是有专业的售前.售后.客服团队 ...
- EasyCV带你复现更好更快的自监督算法-FastConvMAE
简介: 近期FastConvMAE工作在EasyCV框架内首次对外开源,本文将重点介绍ConvMAE和FastConvMAE的主要工作,以及对应的代码实现,最后提供详细的教程示例如何进行FastCon ...
- 阿里云基于全新 RocketMQ 5.0 内核的落地实践
简介: 本篇文章的核心就消息架构以及产品能力的云原生化,介绍了阿里云是如何基于全新的 RocketMQ 5.0 内核做出自己的判断和演进,以及如何适配越来越多的企业客户在技术和能力方面的诉求. 前言 ...
- 深度解析开源推荐算法框架EasyRec的核心概念和优势
简介:如何通过机器学习PAI实现快速构建推荐模型 作者:程孟力 - 机器学习PAI团队 随着移动app的普及,个性化推荐和广告成为很多app不可或缺的一部分.他们在改善用户体验和提升app的收益方面 ...
- [Contract] openzeppelin/cli 开发, 部署, 升级智能合约
Install Dependency $ npm init $ npm install @openzeppelin/cli Setup project $ npx openzeppelin init ...
- WPF 使用 VideoDrawing 播放视频
本文告诉大家如何在 WPF 使用 VideoDrawing 进行视频播放 用这个方法有什么优势?其实只是想作为某个控件的背景,某个控件的背景使用视频而已 控件的背景使用 DrawingBrush 传入 ...
- js部分数组方法
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...