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的并行流很相似;

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

 View Code

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

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

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

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

线程池对比

测试用例1 

 View Code

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

测试用例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)什么时候有空,

 View Code

附加一段全部测试代码

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 那是有一些争议的问题。 虚拟线程协程都是轻量级的线程,它们可以帮助开发人员实现并发编程,但是它们的工作原理和用途略有不同。 Java虚拟线程是在Java虚拟机内部实现的,它们可以帮助提高Java程序的执行效率,但是它们不能跨越多个处理器或核心执行。 相反,Go的协程是在操作系统内部实现的,它们可以跨越多个处理器或核心执行,并且Go的协程有更灵活的内存管理机制,但是它们的执行效率可能不如虚拟线程。 所以,哪个更好取决于您的需求。如果您需要在单个Java虚拟机内部提高程序执行效率,那么虚拟线程可能是更好的选择。如果您需要跨越多个处理器或核心执行,或者希望更灵活的内存管理,那么Go的协程可能是更好的选择。 ### 回答2: Java虚拟线程和Go的协程在实现并发编程方面有不同的特点和优势。 Java虚拟线程是基于操作系统的原生线程实现的,由Java虚拟机(JVM)进行调度和管理。Java线程模型成熟稳定,可以利用操作系统的多核处理器进行并发处理,相对适用于CPU密集型任务。Java线程在多线程编程中更加灵活和强大,通过对线程的控制和同步机制,可以实现较复杂的并发逻辑。 Go的协程是一种轻量级的线程管理机制,实现在Go的运行时环境中。协程由Go语言运行时自行调度,不依赖于操作系统的线程,可以在一个或少量的线程之间高效切换,减少线程切换的开销。Go的协程模型非常适合对I/O密集型任务进行并发处理,可以有效提高程序的响应性能,并节省系统资源。 具体来说,虚拟线程协程在以下方面有所不同: 1. 调度机制:虚拟线程由操作系统进行调度,协程由Go运行时环境进行调度。协程的调度机制更加轻量,切换开销较小。 2. 并发量:虚拟线程在操作系统层面进行并发处理,能够利用多核处理器实现更大规模的并发。协程由于不依赖于操作系统线程,数量可以更多,同时创建和销毁成本也较低。 3. 编程复杂性:Java线程模型更加复杂,需要开发者手动管理线程的创建、销毁和同步。Go的协程模型使用关键字`go`可以方便地创建和管理协程,减少了编程的复杂性。 综上所述,虚拟线程适用于CPU密集型任务,而协程适用于I/O密集型任务。具体选择哪种机制更好取决于应用场景的需求。 ### 回答3: Java虚拟线程和Go的协程都是用来处理并发任务的技术。虚拟线程Java中的线程模型,它通过创建和管理线程来实现并发处理。而协程是Go语言中的并发模型,它是一种轻量级线程,不依赖于操作系统的线程,由Go运行时环境(GOROOT)自己调度。 虚拟线程协程都有各自的优势。虚拟线程Java中被广泛应用,具有较好的可扩展性和稳定性,可以利用操作系统的特性来实现高效的并发处理。但是,由于虚拟线程依赖于操作系统的线程,所以线程切换的开销相对较大,对于大量且频繁的并发任务可能会导致性能下降。 相比之下,Go的协程具有更轻量级的特点,可以在大量协程之间进行高效的切换和调度,从而提高并发处理的效率。同时,协程之间的通信更加简单,可以通过通道(channel)进行同步和数据传递,避免了共享内存的并发问题。此外,Go的协程还支持错误处理和超时机制,使得编写并发程序更加容易。 总的来说,虚拟线程协程都有各自的适用场景。如果项目已经使用了Java,并发任务相对复杂且稳定性要求较高,那么虚拟线程是一个不错的选择;而如果项目需要处理大量且频繁的并发任务,并需要更高的性能和简单的并发编程方式,那么Go的协程可能更加合适。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

野生的狒狒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值