高并发、多线程一直是Java编程中的难点,也是面试题中的要点。Java开发者也一直在尝试使用多线程来解决应用服务器的并发问题。但是多线程并不容易,为此一个新的技术出现了,这就是虚拟线程。

传统多线程的痛点

但是编写多线程代码是非常不容易的,难以控制的执行顺序,共享变量的线程安全性,异常的可观察性等等都是多线程编程的难点。

如果每个请求在请求的持续时间内都在一个线程中处理,那么为了提高应用程序的吞吐量,线程的数量必须随着吞吐量的增长而增长。不幸的是线程是稀缺资源,创建一个线程的代价是昂贵的,即使引入了池化技术也无法降低新线程的创建成本,而且 JDK 当前的线程实现将应用程序的吞吐量限制在远低于硬件可以支持的水平。

为此很多开发人员转向了异步编程,例如CompletableFuture或者现在正热的反应式框架。但是这些技术要么摆脱不了“回调地狱”,要么缺乏可观测性。

解决这些痛点、增强Java平台的和谐,实现每个请求使用独立线程(thread-per-request style)这种风格成为必要之举。能否实现一种“成本低廉”的虚拟线程来映射到系统线程以减少对系统线程的直接操作呢?思路应该是没问题的!于是Java社区发起了关于虚拟线程的JEP 425提案。

虚拟线程

虚拟线程(virtual threads)应该非常廉价而且可以无需担心系统硬件资源被大量创建,并且不应该被池化。应该为每个应用程序任务创建一个新的虚拟线程。因此,大多数虚拟线程将是短暂的并且具有浅层调用堆栈,只执行单个任务 HTTP 客户端调用或单个 JDBC 查询。与之对应的平台线程( Platform Threads,也就是现在传统的JVM线程 )是重量级且昂贵的,因此通常必须被池化。它们往往寿命长,有很深的调用堆栈,并且在许多任务之间共享。

总而言之,虚拟线程保留了与 Java 平台的设计相协调的、可靠的独立请求线程(thread-per-request style),同时优化了硬件的利用。使用虚拟线程不需要学习新概念,甚至需要改掉现在操作多线程的习惯,使用更加容易上手的API、兼容以前的多线程设计、并且丝毫不会影响代码的拓展性。

平台线程和虚拟线程的不同

为了更好理解这一个设计,草案对这两种线程进行了比较。

现在的线程

现在每个java.lang.Thread都是一个平台线程,平台线程在底层操作系统线程上运行 Java 代码,并在代码的整个生命周期内捕获操作系统线程。平台线程数受限于 OS 线程数。

平台线程并不会因为加入虚拟线程而退出历史舞台。

未来的虚拟线程

虚拟线程是由 JDK 而不是操作系统提供的线程的轻量级实现。它们是用户模式线程的一种形式,在其他多线程语言中已经成功(比如Golang中的协程和Erlang中的进程)。 虚拟线程采用 M:N 调度,其中大量 (M) 虚拟线程被调度为在较少数量 (N) 的 OS 线程上运行。 JDK 的虚拟线程调度程序是一种ForkJoinPool工作窃取的机制,以 FIFO 模式运行。

我们可以很随意地创建10000个虚拟线程:

// 预览代码
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
}

无需担心硬件资源是否扛得住,反过来如果你使用Executors.newCachedThreadPool()创建10000个平台线程,在大多数操作系统上很容易因资源不足而崩溃。

为吞吐量而设计

但是这里依然要说明一点,虚拟线程并是为了提升执行速度而设计。它并不比平台线程速度快,它们的存在是为了提供规模(更高的吞吐量),而不是速度(更低的延迟)。它们的数量可能比平台线程多得多,因此根据利特尔定律,它们可以实现更高吞吐量所需的更高并发性。

换句话说,虚拟线程可以显着提高应用程序吞吐量

  • 并发任务的数量很高(超过几千个),并且

  • 工作负载不受 CPU 限制,因为在这种情况下,拥有比处理器内核多得多的线程并不能提高吞吐量。

虚拟线程有助于提高传统服务器应用程序的吞吐量,正是因为此类应用程序包含大量并发任务,这些任务花费大量的时间等待。

增强可观测性

编写清晰的代码并不是全部。对正在运行的程序状态的清晰表示对于故障排除、维护和优化也很重要,JDK 长期以来一直提供调试、分析和监视线程的机制。 在虚拟线程中也会增强代码的可观测性,让开发人员更好地调试代码。

新的线程API

为此增加了新的线程API设计,目前放出的部分如下:

  • Thread.Builder 线程构建器。

  • ThreadFactory 能批量构建相同特性的线程工厂。

  • Thread.ofVirtual() 创建一个虚拟线程。

  • Thread.ofPlatform() 创建一个平台线程。

  • Thread.startVirtualThread(Runnable) 一种创建然后启动虚拟线程的便捷方式。

  • Thread.isVirtual() 测试线程是否是虚拟线程。

还有很多就不一一演示了,有兴趣的自行去看JEP425。

总结

JEP425还有很多的细节,基于我个人理解能力的不足只能解读这么多了。协程在Java社区已经呼唤了很久了,现在终于有了实质性的动作,这是一个令人振奋的好消息。不过这个功能涉及的东西还是很多的,包括平台线程的兼容性、对ThreadLocal的一些影响、对JUC的影响。可能需要多次预览才能最终落地。胖哥可能赶不上那个时候了,不过很多年轻的同学应该能够赶上。

关注公众号:Felordcn 获取更多资讯

个人博客:https://felord.cn

Java的虚拟线程(协程)特性开启预览阶段,多线程开发的难度将大大降低的更多相关文章

  1. 进程&线程&协程

    进程  一.基本概念 进程是系统资源分配的最小单位, 程序隔离的边界系统由一个个进程(程序)组成.一般情况下,包括文本区域(text region).数据区域(data region)和堆栈(stac ...

  2. 多道技术 进程 线程 协程 GIL锁 同步异步 高并发的解决方案 生产者消费者模型

    本文基本内容 多道技术 进程 线程 协程 并发 多线程 多进程 线程池 进程池 GIL锁 互斥锁 网络IO 同步 异步等 实现高并发的几种方式 协程:单线程实现并发 一 多道技术 产生背景 所有程序串 ...

  3. python-socket和进程线程协程(代码展示)

    socket # 一.socket # TCP服务端 import socket # 导入socket tcp_sk = socket.socket() # 实例化一个服务器对象 tcp_sk.bin ...

  4. python并发编程之线程/协程

    python并发编程之线程/协程 part 4: 异步阻塞例子与生产者消费者模型 同步阻塞 调用函数必须等待结果\cpu没工作input sleep recv accept connect get 同 ...

  5. Python并发编程系列之常用概念剖析:并行 串行 并发 同步 异步 阻塞 非阻塞 进程 线程 协程

    1 引言 并发.并行.串行.同步.异步.阻塞.非阻塞.进程.线程.协程是并发编程中的常见概念,相似却也有却不尽相同,令人头痛,这一篇博文中我们来区分一下这些概念. 2 并发与并行 在解释并发与并行之前 ...

  6. C后端设计开发 - 第3章-气功-原子锁线程协程

    正文 第3章-气功-原子锁线程协程 后记 如果有错误, 欢迎指正. 有好的补充, 和疑问欢迎交流, 一块提高. 在此谢谢大家了. 童话镇 - http://music.163.com/#/m/song ...

  7. Python 进程线程协程 GIL 闭包 与高阶函数(五)

    Python 进程线程协程 GIL 闭包 与高阶函数(五) 1 GIL线程全局锁 ​ 线程全局锁(Global Interpreter Lock),即Python为了保证线程安全而采取的独立线程运行的 ...

  8. python自动化开发学习 进程, 线程, 协程

    python自动化开发学习 进程, 线程, 协程   前言 在过去单核CPU也可以执行多任务,操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换任务2,任务2执行0.01秒,在切换到任务3,这 ...

  9. java压缩包上传,解压,预览(利用editor.md和Jstree实现)和下载

    java压缩包上传,解压,预览(利用editor.md和Jstree实现)和下载 实现功能:zip文件上传,后台自动解压,Jstree树目录(遍历文件),editor.md预览 采用Spring+Sp ...

随机推荐

  1. 2.8 C++STL set/multiset容器详解

    文章目录 2.8.1 引入 2.8.2 代码示例 2.8.3 代码运行结果 2.8.4 对组pair的补充 代码实例 运行结果 总结 2.8.1 引入 set/multiset容器概念 set和mul ...

  2. 2022年官网下安装Studio 3T最全版与官网查阅方法(无需注册下载版)

    目录 一.环境 1.构建工具(参考工具部署方式) 2.保持启动 二.下载安装 1.百度搜索,或者访问官网:https://robomongo.org/,选择下载进入下载页. 2.进入下载页,选择如下下 ...

  3. CSI 工作原理与JuiceFS CSI Driver 的架构设计详解

    容器存储接口(Container Storage Interface)简称 CSI,CSI 建立了行业标准接口的规范,借助 CSI 容器编排系统(CO)可以将任意存储系统暴露给自己的容器工作负载.Ju ...

  4. Oracle视图(view)传参数教程

    废话不多说,直接上例子! 创建包: create or replace package p_view_param is function set_param(num number) return nu ...

  5. Flask 之 宏

    宏 对宏(macro)的理解: 把它看作 Jinja2 中的一个函数,它会返回一个模板或者 HTML 字符串 为了避免反复地编写同样的模板代码,出现代码冗余,可以把他们写成函数以进行重用 需要在多处重 ...

  6. 论文解读(SUGRL)《Simple Unsupervised Graph Representation Learning》

    Paper Information Title:Simple Unsupervised Graph Representation LearningAuthors: Yujie Mo.Liang Pen ...

  7. RocketMq基础 看这一篇就够了

    RocketMQ 编译安装 HelloWorld 官方网站 http://rocketmq.apache.org GitHub https://github.com/apache/rocketmq Q ...

  8. UVA1389 Hard Life (01分数规划+最大流)

    UVA1389 Hard Life (01分数规划+最大流) Luogu 题目描述略 题解时间 $ (\frac{\Sigma EdgeCount}{\Sigma PointCount})_{max} ...

  9. jQuery--子元素过滤

    1.子元素过滤器介绍 :nth-child 获得指定索引的孩子,从1开始 :first-child 获得第一个孩子 :last-child 获得最后一个孩子 :only-child 获得独生子 2.代 ...

  10. 可以直接调用 Thread 类的 run ()方法么?

    当然可以.但是如果我们调用了 Thread 的 run()方法,它的行为就会和普通的方 法一样,会在当前线程中执行.为了在新的线程中执行我们的代码,必须使用 Thread.start()方法.