Loom 的一些思考注意事项

/

为什么 Java 在即有的平台线程模型上,又推出了虚拟线程的模型?

主要还是 Java 本身的平台线程已经达到了一个 JVM 对于线程的管理和调度的瓶颈,所以才有了非官方的 Fiber(纤程)Spring Webflux 这种对同步代码影响很大的模型(对代码编写复杂性更高了), 但是 Java 本身对类似 async/await Future.then 这种写法并不感兴趣,因为这种代码明显会增加了代码本身的复杂性,比如忘记等待 await 的结果,导致了读取到了上一次数据。所以 Java 就在现有的线程模型上引入了更加易用的虚拟线程模型,及让 JVM 本身去调度堵塞的同步代码片,而不是一一的映射上内核线程,导致在高并发的大量的等待的内核线程,带来内核线程调用的低效的问题,及通过对堵塞线程的判断,来在 JVM 内存切换虚拟线程的执行的顺序,及对 ForkJoinPool 进行更加深层的调用。来达到了高效的异步线程对 CPU 的利用,同时呢对现有同步就是最简单的异步解决方法。而不是交给程序员自身去考虑是否需要 .then() ,来徒增代码的复杂性

平台线程(Platform Thread) vs 虚拟线程(Virtaul Thread)

Java 的虚拟线程本质上对应还是平台线程,只是通过特殊手段实现了,在一个线程上同时绑定多个异步执行的任务,也就是最原始的 ForkJoinPool 这种线程池的工作原理,及把一段同步线程代码通过进行简单的任务分拆,在互不影响的情况下,进行并发的执行,从而更加的高效的利用 CPU 资源,在传统的线程模型(平台线程)里面,通过如果一段代码卡住了,就约等于当前线程卡住了,当前线程也并不会释放对系统线程的引用持有, 一个简单的例子就是,如果我们想用同时抓取 100 图片资源,那么我们就必须启动 100 个平台线程,而这个平台线程由于对应上了系统的内核的线程,所以无论是启动还是停止都会占用较大的系统资源,比如 Java 默认的一个线程就需要分配 512KB 的内存使用,用于存储该线程的上下文信息,所以这种线程模型虽然调度简单 (1比1的对应系统的内核线程),但是在需要高并发的场景下,这种平台线程的高可用性并不能很好的解决,除了不停的堆资源外

使用虚拟线程的注意事项

虚拟线程虽然好,但是也需要注意使用场景,避免达不到实际的效果

CPU 密集计算型逻辑不要使用虚拟线程

虚拟程序的快速创建和销毁,所以并非效果非常的好,但是有些同步代码并不能使当前的虚拟线程挂起(放弃和平台线程的绑定),比如最常见的 CPU 运算(计算 36 的 12次方),这种使用虚拟线程就会导致一直不释放,进而导致需要不停的无用调度,同时可能导致系统本地对虚拟线程调度变的缓慢,进而导致其他本该快速执行的网络请求这种 IO 型的虚拟线程也不能高效的执行,导致整体系统的不稳定型

对端的 IO 服务对并发数量有限制时

这种情况如果使用 VThread 可能会导致对方服务进行频率限制,从而导致不提供服务,而变的低效率,额外增加代码对异常情况的处理(及有限制的 VThread 并发执行),进而在使用会使得代码变的复杂,比如需要虚拟线程代码内实现对并非的控制代码,比如添加 Semaphore 或者是 RateLimit 相关的逻辑

Virtaul Thread 会增加异常分析(Crash)的排错难度

由于使用了 Loom 后,我们通过 Jstack 或者 Hprof 文件里面获取到的线程栈信息可能并不是直接的对应上我们代码本身的线程栈的,所以对应这种情况可能会增加额外的排错难度,这个问题可能需要相应的工具更新后,或许这方面的问题能得到解决

不应该池化虚拟线程

由于虚拟线程通常用于一次 IO 请求,比如抓起一张图片,或者调用一次 Redis 的查询,这种通常很快就能完成执行,所以没有必要去池化这个虚拟线程,及将虚拟线程交给传统平台线程池去执行,因为这样会徒增平台线程的数量和相关的调用,是一种变相资源错配

如果以前的代码使用的线程池,可以无缝改为 Executors.newVirtualThreadPerTaskExecutor 这个专门用于虚拟线程池的线程池来执行以前的可异步的代码,但是还要注意上面说的 IO 密集型不要使用这个虚拟线程池来执行

synchronized 关键字

使用 synchronized 本身并不会导致 Virtaul Thread 的执行结果和 Platform Thread 不一致,但是 synchronized 这个会导致直接挂起对应的平台线程,及并不会放弃对平台线程的持有,及度占用了这个一个平台线程,这样可能会导致 虚拟线程池额外的需要启用更多的平台线程来达到更高效的工作,进而加剧了对资源的占用,所以应该将 synchronized ,改为堵塞更小 ReentrantLock 的 tryLock() 来实多线程的同步或者线程安全

相关资料

转载请注明作者和出处,并添加本页链接。
原文链接: //xiaochun.zrlog.com/java-loom-think.html