前言

众所周知,java 是没有协程线程的,在我们如此熟知的jdk 1.8时代,大佬们想出来的办法就是异步io,甚至用并行的stream流来实现,高并发也好,缩短事件处理时间也好;大家都在想着自己认为更好的实现方式;

在来说说吧,我为什么会在今天研究这个破b玩意儿呢,

这事情还的从一个月前的版本维护说起,

目前公司游戏运营的算中规中矩吧,日新增和日活跃用户基本保持在1w,2.5w样子;

大概1-2周会有一次版本更新,需要停服维护的,

我想大部分做游戏的同僚可能都知道,游戏架构里面包含一个登录服这么一个环节,用于对账号管理以及和sdk平台做登录二次验证;

我们的问题也就出在了这sdk二次登录验证环境;

从这个截图中不难看出,我在向sdk服务器进行验证的时候http请求耗时,一个请求多长达400ms,按照这个逻辑,一个线程一秒钟也只能是2个登录;

然后面对停服维护阶段,玩家疯狂的尝试登录,导致登录服务器直接积压了30万个登录请求等待处理;

在寻求方案的时候,看到了http请求池化方案,目前已经大线程池(这里是本人自定义线程池)和http池化(基于 Apache CloseableHttpClient)处理方案 因为平台是jdk11的

在寻求方案同时发现了jdk19开放的预览版新功能虚拟线程;翻阅了一些资料,就像这虚拟线程能不能为我带来更好性能体验,让现有的系统,吞吐量更上一层楼;

一下测试代码用的是jdk20测试

构建虚拟线程

第一步我们需要先创建虚拟线程,才能去理解什么是虚拟线程

1     public static void main(String[] args) throws Exception {
2
3 Thread.startVirtualThread(() -> {
4 System.out.println(Thread.currentThread().toString());
5 });
6
7 Thread.sleep(3000);
8 }

这就正确的启动了一个虚拟线程;从线程明明输出看着是不是有点眼熟,是不是跟stream的并行流很相似;

接下来我们看看虚拟线程的运行是怎么回事,

 1    public static void main(String[] args) throws Exception {
2
3 Thread.startVirtualThread(() -> {
4 try {
5 Thread.sleep(5000);
6 } catch (InterruptedException e) {
7 throw new RuntimeException(e);
8 }
9 System.out.println(Thread.currentThread().toString());
10 });
11
12 Thread.startVirtualThread(() -> {
13 try {
14 Thread.sleep(5000);
15 } catch (InterruptedException e) {
16 throw new RuntimeException(e);
17 }
18 System.out.println(Thread.currentThread().toString());
19 });
20 Thread.startVirtualThread(() -> {
21 try {
22 Thread.sleep(5000);
23 } catch (InterruptedException e) {
24 throw new RuntimeException(e);
25 }
26 System.out.println(Thread.currentThread().toString());
27 });
28 Thread.startVirtualThread(() -> {
29 try {
30 Thread.sleep(5000);
31 } catch (InterruptedException e) {
32 throw new RuntimeException(e);
33 }
34 System.out.println(Thread.currentThread().toString());
35 });
36 Thread.startVirtualThread(() -> {
37 try {
38 Thread.sleep(5000);
39 } catch (InterruptedException e) {
40 throw new RuntimeException(e);
41 }
42 System.out.println(Thread.currentThread().toString());
43 });
44 Thread.sleep(3000);
45 }

我们多new几个虚拟线程来看看监控

看到了吧,实际上你new的虚拟线程,其实是被当成了一个任务丢到了线程池里面在运行;

在翻阅了现有的代码逻辑还不能定义这个底部线程池,只能使用默认的;

当然目前是预览版,不确定之后会不会可以自定义实现,stream流一样,可以定义它并行数量;

线程池对比

测试用例1

 1     @Test
2 public void r() {
3 t1();
4 t2();
5 }
6
7 public void t1() {
8 AtomicInteger atomicInteger = new AtomicInteger(100);
9 try (var executor = Executors.newFixedThreadPool(10)) {
10 long nanoTime = System.nanoTime();
11 for (int i = 0; i < 100; i++) {
12 executor.execute(() -> {
13 try {
14 Thread.sleep(50);
15 } catch (InterruptedException e) {
16 throw new RuntimeException(e);
17 }
18 atomicInteger.decrementAndGet();
19 });
20 }
21 while (atomicInteger.get() > 0) {}
22 System.out.println("平台线程 - " + atomicInteger.get() + " - " + ((System.nanoTime() - nanoTime) / 10000 / 100f));
23 }
24 }
25
26 public void t2() {
27 AtomicInteger atomicInteger = new AtomicInteger(100);
28 try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
29 long nanoTime = System.nanoTime();
30 for (int i = 0; i < 100; i++) {
31 executor.execute(() -> {
32 try {
33 Thread.sleep(50);
34 } catch (InterruptedException e) {
35 throw new RuntimeException(e);
36 }
37 atomicInteger.decrementAndGet();
38 });
39 }
40 while (atomicInteger.get() > 0) {}
41 System.out.println("虚拟线程 - " + atomicInteger.get() + " - " + ((System.nanoTime() - nanoTime) / 10000 / 100f));
42 }
43 }

通过这段测试代码对比,总任务耗时,显而易见性能;

测试用例2

 1     public void t2p() {
2 Runnable runnable = () -> {
3 long g = 0;
4 for (int i = 0; i < 10000; i++) {
5 for (int j = 0; j < 10000; j++) {
6 for (int k = 0; k < 100; k++) {
7 g++;
8 }
9 }
10 }
11 };
12 AtomicInteger atomicInteger = new AtomicInteger(100);
13 try (var executor = Executors.newFixedThreadPool(10)) {
14 long nanoTime = System.nanoTime();
15 for (int i = 0; i < 100; i++) {
16 executor.execute(() -> {
17 runnable.run();
18 atomicInteger.decrementAndGet();
19 });
20 }
21 while (atomicInteger.get() > 0) {}
22 System.out.println("平台线程 - " + atomicInteger.get() + " - " + ((System.nanoTime() - nanoTime) / 10000 / 100f));
23 }
24 }
25
26 public void t2v() {
27 Runnable runnable = () -> {
28 long g = 0;
29 for (int i = 0; i < 10000; i++) {
30 for (int j = 0; j < 10000; j++) {
31 for (int k = 0; k < 100; k++) {
32 g++;
33 }
34 }
35 }
36 };
37 AtomicInteger atomicInteger = new AtomicInteger(100);
38 try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
39 long nanoTime = System.nanoTime();
40 for (int i = 0; i < 100; i++) {
41 executor.execute(() -> {
42 runnable.run();
43 atomicInteger.decrementAndGet();
44 });
45 }
46 while (atomicInteger.get() > 0) {}
47 System.out.println("虚拟线程 - " + atomicInteger.get() + " - " + ((System.nanoTime() - nanoTime) / 10000 / 100f));
48 }
49 }

通过测试用例2不难看出,虚拟线程已经不占优势;

这是为什么呢?

总结

平台线程我就不过多描述因为大家都知道,网上的描述也特别多;

虚拟线程,其实我们更多可以可以考虑他只是一个任务,异步的任务;

区别在于,平台线程受制于cpu,如果你执行任务很耗时或者比如网络io等挂起等待,那么这个cpu也会一直挂起等待无法处理其他事情;

虚拟线程是异步任务凌驾于平台线程之上,也就是说,当你的虚拟线程等待挂起的时候,平台线程就去执行其他任务(其他虚拟线程)去了

我们通过上面测试用例可以这样理解,

用例1,通常我们的RPC服务或者SDK跟我开通SDK二次验证大部分时间处于等待挂起业务,这时候虚拟线程的作用就会非常大,他可以发起大量的验证请求,等待回答;我们通常定义的IO密集型应用;

用例2,属于计算型的,它会一直占用cpu时间片,不会腾出cpu去执行其他事件;我们通常说cpu密集型应用不太适用虚拟线程;

目前虚拟线程的执行依赖于底层线程池,我们无法自主控制它,所以不是很建议使用

关于虚拟线程的描述或者定义我就不在过多的去阐述,

我只说一下它运行的逻辑吧,

1,在不同时间段一个虚拟线程可以由不同的平台线程调度,也可以由一个平台线程调度,平台线程=系统线程=cpu

2,在不同时间段一个平台线程在可以调度不同的虚拟线程,也可以反复调度一个虚拟线程

3,在同一时间段,一个平台线程只能调用一个虚拟线程,一个虚拟线程只能由一个平台线程调度

换言之,其实虚拟线程可以看成一个task,你可以new很多的task,至于他什么时候被执行,就看你的工人(cpu)什么时候有空,

  1 package code.threading;
2
3 import org.junit.Test;
4
5 import java.util.ArrayList;
6 import java.util.List;
7 import java.util.concurrent.Executors;
8 import java.util.concurrent.atomic.AtomicBoolean;
9 import java.util.concurrent.atomic.AtomicInteger;
10
11 /**
12 * 线程测试
13 *
14 * @author: Troy.Chen(無心道, 15388152619)
15 * @version: 2023-05-29 21:31
16 **/
17 public class ThreadCode {
18
19 public static void main(String[] args) throws Exception {
20
21 }
22
23 @Test
24 public void s() throws Exception {
25
26 Runnable runnable = () -> {
27 long nanoTime = System.nanoTime();
28 long g = 0;
29 for (int i = 0; i < 10000; i++) {
30 for (int j = 0; j < 10000; j++) {
31 for (int k = 0; k < 100; k++) {
32 g++;
33 }
34 }
35 }
36 Thread thread = Thread.currentThread();
37 System.out.println(g + " - " + thread.isVirtual() + " - " + thread.threadId() + " - " + ((System.nanoTime() - nanoTime) / 10000 / 100f));
38 };
39
40 List<VirtualThread> ts = new ArrayList<>();
41 ts.add(new VirtualThread(runnable));
42 ts.add(new VirtualThread(runnable));
43 ts.add(new VirtualThread(runnable));
44 ts.add(new VirtualThread(runnable));
45 ts.add(new VirtualThread(runnable));
46 ts.add(new VirtualThread(runnable));
47 ts.add(new VirtualThread(runnable));
48 ts.add(new VirtualThread(runnable));
49 ts.add(new VirtualThread(runnable));
50 ts.add(new VirtualThread(runnable));
51 ts.add(new VirtualThread(runnable));
52 ts.add(new VirtualThread(runnable));
53 ts.add(new VirtualThread(runnable));
54 ts.add(new VirtualThread(runnable));
55 ts.add(new VirtualThread(runnable));
56 ts.add(new VirtualThread(runnable));
57 ts.add(new VirtualThread(runnable));
58 ts.add(new VirtualThread(runnable));
59 ts.add(new VirtualThread(runnable));
60 ts.add(new VirtualThread(runnable));
61 for (VirtualThread t : ts) {
62 t.shutdown();
63 }
64 for (VirtualThread t : ts) {
65 t.join();
66 }
67 }
68
69 public static class VirtualThread implements Runnable {
70
71 /*虚拟线程构建器*/
72 static final Thread.Builder.OfVirtual ofVirtual = Thread.ofVirtual().name("v-", 1);
73
74 AtomicBoolean shutdown = new AtomicBoolean();
75 Thread _thread;
76 Runnable runnable;
77
78 public VirtualThread(Runnable runnable) {
79 this.runnable = runnable;
80 _thread = ofVirtual.start(this);
81 }
82
83 @Override public void run() {
84 do {
85 try {
86 try {
87 this.runnable.run();
88 } catch (Throwable e) {
89 e.printStackTrace();
90 }
91 } catch (Throwable throwable) {}
92 } while (!shutdown.get());
93 System.out.println("虚拟线程退出 " + _thread.isVirtual() + " - " + _thread.threadId() + " - " + _thread.getName());
94 }
95
96 public void shutdown() {
97 shutdown.lazySet(true);
98 }
99
100 public void join() throws InterruptedException {
101 _thread.join();
102 }
103 }
104
105 @Test
106 public void r() {
107 t2p();
108 t2v();
109 }
110
111 public void t1p() {
112 AtomicInteger atomicInteger = new AtomicInteger(100);
113 try (var executor = Executors.newFixedThreadPool(10)) {
114 long nanoTime = System.nanoTime();
115 for (int i = 0; i < 100; i++) {
116 executor.execute(() -> {
117 try {
118 Thread.sleep(50);
119 } catch (InterruptedException e) {
120 throw new RuntimeException(e);
121 }
122 atomicInteger.decrementAndGet();
123 });
124 }
125 while (atomicInteger.get() > 0) {}
126 System.out.println("平台线程 - " + atomicInteger.get() + " - " + ((System.nanoTime() - nanoTime) / 10000 / 100f));
127 }
128 }
129
130 public void t1v() {
131 AtomicInteger atomicInteger = new AtomicInteger(100);
132 try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
133 long nanoTime = System.nanoTime();
134 for (int i = 0; i < 100; i++) {
135 executor.execute(() -> {
136 try {
137 Thread.sleep(50);
138 } catch (InterruptedException e) {
139 throw new RuntimeException(e);
140 }
141 atomicInteger.decrementAndGet();
142 });
143 }
144 while (atomicInteger.get() > 0) {}
145 System.out.println("虚拟线程 - " + atomicInteger.get() + " - " + ((System.nanoTime() - nanoTime) / 10000 / 100f));
146 }
147 }
148
149 public void t2p() {
150 Runnable runnable = () -> {
151 long g = 0;
152 for (int i = 0; i < 10000; i++) {
153 for (int j = 0; j < 10000; j++) {
154 for (int k = 0; k < 100; k++) {
155 g++;
156 }
157 }
158 }
159 };
160 AtomicInteger atomicInteger = new AtomicInteger(100);
161 try (var executor = Executors.newFixedThreadPool(10)) {
162 long nanoTime = System.nanoTime();
163 for (int i = 0; i < 100; i++) {
164 executor.execute(() -> {
165 runnable.run();
166 atomicInteger.decrementAndGet();
167 });
168 }
169 while (atomicInteger.get() > 0) {}
170 System.out.println("平台线程 - " + atomicInteger.get() + " - " + ((System.nanoTime() - nanoTime) / 10000 / 100f));
171 }
172 }
173
174 public void t2v() {
175 Runnable runnable = () -> {
176 long g = 0;
177 for (int i = 0; i < 10000; i++) {
178 for (int j = 0; j < 10000; j++) {
179 for (int k = 0; k < 100; k++) {
180 g++;
181 }
182 }
183 }
184 };
185 AtomicInteger atomicInteger = new AtomicInteger(100);
186 try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
187 long nanoTime = System.nanoTime();
188 for (int i = 0; i < 100; i++) {
189 executor.execute(() -> {
190 runnable.run();
191 atomicInteger.decrementAndGet();
192 });
193 }
194 while (atomicInteger.get() > 0) {}
195 System.out.println("虚拟线程 - " + atomicInteger.get() + " - " + ((System.nanoTime() - nanoTime) / 10000 / 100f));
196 }
197 }
198 }

附加一段全部测试代码

java协程线程之虚拟线程的更多相关文章

  1. java主线程等待所有子线程执行完毕在执行(常见面试题)

    java主线程等待所有子线程执行完毕在执行(常见面试题) java主线程等待所有子线程执行完毕在执行,这个需求其实我们在工作中经常会用到,比如用户下单一个产品,后台会做一系列的处理,为了提高效率,每个 ...

  2. Java协程编程之Loom项目尝鲜

    前提 之前很长一段时间关注JDK协程库的开发进度,但是前一段时间比较忙很少去查看OpenJDK官网的内容.Java协程项目Loom(因为项目还在开发阶段,OpenJDK给出的官网https://ope ...

  3. Java协程实践指南(一)

    一. 协程产生的背景 说起协程,大多数人的第一印象可能就是GoLang,这也是Go语言非常吸引人的地方之一,它内建的并发支持.Go语言并发体系的理论是C.A.R Hoare在1978年提出的CSP(C ...

  4. 关于Unity中协程、多线程、线程锁、www网络类的使用

    协程 我们要下载一张图片,加载一个资源,这个时候一定不是一下子就加载好的,或者说我们不一定要等它下载好了才进行其他操作,如果那样的话我就就卡在了下载图片那个地方,傻住了.我们希望我们只要一启动加载的命 ...

  5. java主线程结束和子线程结束之间的关系

    (一)Main线程是个非守护线程,不能设置成守护线程. 这是因为,main线程是由java虚拟机在启动的时候创建的.main方法开始执行的时候,主线程已经创建好并在运行了.对于运行中的线程,调用Thr ...

  6. UNITY所谓的异步加载几乎全部是协程,不是线程;MAP3加载时解压非常慢

    实践证明,以下东西都是协程,并非线程(thread): 1,WWW 2,AssetBundle.LoadFromFileAsync 3,LoadSceneAsync 其它未经测试 此问题的提出是由于一 ...

  7. Java主线程如何等待子线程执行结束(转)

    工作中往往会遇到异步去执行某段逻辑, 然后先处理其他事情, 处理完后再把那段逻辑的处理结果进行汇总的产景, 这时候就需要使用线程了. 一个线程启动之后, 是异步的去执行需要执行的内容的, 不会影响主线 ...

  8. Java主线程等待所有子线程执行完毕再执行解决办法(转)

    方法一: Thread.join()方法,亲测可行,thread.join()方法 Vector<Thread> ts = new Vector<Thread>(); for  ...

  9. JAVA协程 纤程 与Quasar 框架

    ava使用的是系统级线程,也就是说,每次调用new Thread(....).run(),都会在系统层面建立一个新的线程,然鹅新建线程的开销是很大的(每个线程默认情况下会占用1MB的内存空间,当然你愿 ...

  10. java 协程

    协程是比线程更轻量级的程序处理单元,也可以说是运行在线程上的线程,由自己控制 1.适用于被阻塞的,且需要大量并发的场景. 2.不适用于,大量计算的多线程,遇到此种情况,更好实用线程去解决. 虽然Jav ...

随机推荐

  1. 【vue3-element-admin 】基于 Vue3 + Vite4 + TypeScript + Element-Plus 从0到1搭建后台管理系统(前后端开源@有来开源组织)

    vue3-element-admin 是基于 vue-element-admin 升级的 Vue3 + Element Plus 版本的后台管理前端解决方案,技术栈为 Vue3 + Vite4 + T ...

  2. OpenAI的子词标记化神器--tiktoken 以及 .NET 支持库SharpToken

    OpenAI在其官方GitHub上公开了一个开源Python库:tiktoken,这个库主要是用力做字节编码对的. 字节编码对(Byte Pair Encoder,BPE)是一种子词处理的方法.其主要 ...

  3. [MAUI]模仿网易云音乐黑胶唱片的交互实现

    @ 目录 创建页面布局 创建手势控件 创建影子控件 唱盘拨动交互 唱盘和唱针动画 项目地址 用过网易云音乐App的同学应该都比较熟悉它播放界面. 这是一个良好的交互设计,留声机的界面隐喻准确地向人们传 ...

  4. 深度学习-07(图像分类、常用数据集、利用CNN实现图像分类、图像分类优化)

    文章目录 深度学习-07(PaddlePaddle图像分类) 图像分类概述 概述 什么是图像分类 图像分类粒度 图像分类发展历程 图像分类问题的挑战 常用数据集介绍 MNIST数据集 CIFAR10数 ...

  5. jdk环境配置已经版本切换

    下载安装jdk到电脑盘,两个版本要下载到一个盘里面下安装好后右击我的电脑 找到高级系统设置,点开环境变量 新建环境变量 总计环境变量一共添加四个其中 java8和java11是是选择文件路径 JAVA ...

  6. 前端Vue项目打包性能优化方案

    一.前言 Vue 框架通过数据双向绑定和虚拟 DOM 技术,帮我们处理了前端开发中最脏最累的 DOM 操作部分, 我们不再需要去考虑如何操作 DOM 以及如何最高效地操作 DOM:但 Vue 项目中仍 ...

  7. 2020-09-25:rust中Point是结构体类型,【let p1=Point{x:25,y:25};let p2=p1;】请问p1和p2是同一个对象吗?

    福哥答案2020-09-25:#福大大架构师每日一题# 不是同一个对象.p1和p2直接存内容,不存地址.这点跟java里的new对象是不一样的.p1.x和p1的地址相同,说明p1存的是内容,而不是地址 ...

  8. 2022-06-01:给定一个数组arr,可能有正、有负、有0,无序。 只能挑选两个数字,想尽量让两个数字加起来的绝对值尽量小。 返回可能的最小的值。

    2022-06-01:给定一个数组arr,可能有正.有负.有0,无序. 只能挑选两个数字,想尽量让两个数字加起来的绝对值尽量小. 返回可能的最小的值. 答案2022-06-01: 排序,双指针. 代码 ...

  9. 2021-08-25:给定数组father大小为N,表示一共有N个节点,father[i] = j 表示点i的父亲是点j, father表示的树一定是一棵树而不是森林,queries是二维数组,大小为

    2021-08-25:给定数组father大小为N,表示一共有N个节点,father[i] = j 表示点i的父亲是点j, father表示的树一定是一棵树而不是森林,queries是二维数组,大小为 ...

  10. 2021-10-21:Excel 表列序号。给你一个字符串 columnTitle ,表示 Excel 表格中的列名称。返回该列名称对应的列序号。示例 1:输入: columnTitle = “A“,

    2021-10-21:Excel 表列序号.给你一个字符串 columnTitle ,表示 Excel 表格中的列名称.返回该列名称对应的列序号.示例 1:输入: columnTitle = &quo ...