java协程线程之虚拟线程
前言
众所周知,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协程线程之虚拟线程的更多相关文章
- java主线程等待所有子线程执行完毕在执行(常见面试题)
java主线程等待所有子线程执行完毕在执行(常见面试题) java主线程等待所有子线程执行完毕在执行,这个需求其实我们在工作中经常会用到,比如用户下单一个产品,后台会做一系列的处理,为了提高效率,每个 ...
- Java协程编程之Loom项目尝鲜
前提 之前很长一段时间关注JDK协程库的开发进度,但是前一段时间比较忙很少去查看OpenJDK官网的内容.Java协程项目Loom(因为项目还在开发阶段,OpenJDK给出的官网https://ope ...
- Java协程实践指南(一)
一. 协程产生的背景 说起协程,大多数人的第一印象可能就是GoLang,这也是Go语言非常吸引人的地方之一,它内建的并发支持.Go语言并发体系的理论是C.A.R Hoare在1978年提出的CSP(C ...
- 关于Unity中协程、多线程、线程锁、www网络类的使用
协程 我们要下载一张图片,加载一个资源,这个时候一定不是一下子就加载好的,或者说我们不一定要等它下载好了才进行其他操作,如果那样的话我就就卡在了下载图片那个地方,傻住了.我们希望我们只要一启动加载的命 ...
- java主线程结束和子线程结束之间的关系
(一)Main线程是个非守护线程,不能设置成守护线程. 这是因为,main线程是由java虚拟机在启动的时候创建的.main方法开始执行的时候,主线程已经创建好并在运行了.对于运行中的线程,调用Thr ...
- UNITY所谓的异步加载几乎全部是协程,不是线程;MAP3加载时解压非常慢
实践证明,以下东西都是协程,并非线程(thread): 1,WWW 2,AssetBundle.LoadFromFileAsync 3,LoadSceneAsync 其它未经测试 此问题的提出是由于一 ...
- Java主线程如何等待子线程执行结束(转)
工作中往往会遇到异步去执行某段逻辑, 然后先处理其他事情, 处理完后再把那段逻辑的处理结果进行汇总的产景, 这时候就需要使用线程了. 一个线程启动之后, 是异步的去执行需要执行的内容的, 不会影响主线 ...
- Java主线程等待所有子线程执行完毕再执行解决办法(转)
方法一: Thread.join()方法,亲测可行,thread.join()方法 Vector<Thread> ts = new Vector<Thread>(); for ...
- JAVA协程 纤程 与Quasar 框架
ava使用的是系统级线程,也就是说,每次调用new Thread(....).run(),都会在系统层面建立一个新的线程,然鹅新建线程的开销是很大的(每个线程默认情况下会占用1MB的内存空间,当然你愿 ...
- java 协程
协程是比线程更轻量级的程序处理单元,也可以说是运行在线程上的线程,由自己控制 1.适用于被阻塞的,且需要大量并发的场景. 2.不适用于,大量计算的多线程,遇到此种情况,更好实用线程去解决. 虽然Jav ...
随机推荐
- Springboot整合Flowable6.x导出bpmn20
项目源码仓库 BPMN2.0(Business Process Model and Notation)是一套业务流程模型与符号建模标准,以XML为载体,以符号可视化业务,支持精准的执行语义来描述元素的 ...
- Linux shell和环境变量
环境变量 存储有关shell会话和工作环境信息:允许在内存中存储数据. 注意什么时候要用$ 什么时候不用$:用到变量,需要$;操作变量,不需要$.printenv除外 分为两类: 全局变量:对shel ...
- SpringBoot导出Word文档的三种方式
SpringBoot导出Word文档的三种方式 一.导出方案 1.直接在Java代码里创建Word文档,设置格式样式等,然后导出.(略) 需要的见:https://blog.csdn.net/qq_4 ...
- 2022-02-07:k8s安装mysql,yaml如何写?(非面试题)
2022-02-07:k8s安装mysql,yaml如何写?(非面试题) 答案2022-02-07: yaml如下: apiVersion: apps/v1 kind: Deployment meta ...
- 2022-01-13:K 个不同整数的子数组。 给定一个正整数数组 A,如果 A 的某个子数组中不同整数的个数恰好为 K,则称 A 的这个连续、不一定不同的子数组为好子数组。 (例如,[1,2,3,1
2022-01-13:K 个不同整数的子数组. 给定一个正整数数组 A,如果 A 的某个子数组中不同整数的个数恰好为 K,则称 A 的这个连续.不一定不同的子数组为好子数组. (例如,[1,2,3,1 ...
- 2021-06-20:已知一个消息流会不断地吐出整数 1~N,但不一定按照顺序依次吐出。如果上次打印的序号为i, 那么当i+1出现时,请打印 i+1 及其之后接收过的并且连续的所有数,直到1~N全部接
2021-06-20:已知一个消息流会不断地吐出整数 1~N,但不一定按照顺序依次吐出.如果上次打印的序号为i, 那么当i+1出现时,请打印 i+1 及其之后接收过的并且连续的所有数,直到1~N全部接 ...
- CU002HModel matching query does not exist.
问题描述:CU002HModel matching query does not exist. 问题分析:匹配的查询不存在.顾名思义就是什么数据都没有. 原因是get查询时没有结果会报错,所以有两个选 ...
- SQL:DATEDIFF和DATEADD函数
DATEDIFF和DATEADD函数.DATEDIFF函数计算两个日期之间的小时.天.周.月.年等时间间隔总数.DATEADD函数计算一个日期通过给时间间隔加减来获得一个新的日期.要了解更多的DATE ...
- MMCM and PLL Dynamic Reconfiguration
Reconfiguration is performed through the DRP. The DRP provides access to the configuration bits that ...
- 【GiraKoo】Visual Studio开启Asan提示“LINK : fatal error LNK1104: cannot open file 'LIBVCASAN.lib'”
[解决]Visual Studio开启Asan提示"LINK fatal error LNK1104 cannot open file 'LIBVCASAN.lib'" 环境 Vi ...