前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >虚拟线程目前不推荐上生产的个人思考

虚拟线程目前不推荐上生产的个人思考

作者头像
干货满满张哈希
发布2024-05-25 09:03:46
500
发布2024-05-25 09:03:46
举报
1. pin 线程引发的问题比预期严重,需要修改的库繁多

截止目前 Java 21 虚拟线程一些比较严重的 Bug:

1. `Thread.HoldsLock(Object)` 这个方法,如果是虚拟线程调用,会在平台线程获取到锁之后,就算切换虚拟线程,也会返回 true:https://bugs.openjdk.org/browse/JDK-8281642

2. 默认使用的 ForkJoinPool.common 线程池,如果全部 pin 住,问题很严重。比如synchronized 块外有与块内争用的资源,可能会导致死锁(这个与原来的线程池池化死锁类似)。主要是 monitor enter 的机制与虚拟线程的 Continuation 设计以及 ForkJoinPool 的设计目前不太兼容:https://bugs.openjdk.org/browse/JDK-8320211

目前看来,synchronized pin 线程的问题比预期的更严重,很多库,包括 JDK 库本身可能都要兼容:

1. JDK 库本身:

(1)需要重新看看 AQS 的设计与虚拟线程的兼容性,尤其是队列,防止出现队列调度死锁

(2)需要审查下 ForkJoinPool 的设计,将 ForkJoinPool 作为默认的 Carrier 线程池,是否合适。因为工作窃取导致调度设计困难,并且,ForkJoinPool 天生不支持 Go 那种遇到 Pin 线程新起一个线程的解决方案。我个人觉得,需要那种能手动指定虚拟线程的负载线程池的方案

(3)很多 synchronized 的代码是否要重写,尤其是常用的数据结构以及输出流的地方

2. 各种 Java 库的兼容:日常开发离不开 JDBC 库,但是官方的 JDBC 库里面很多 synchronized 以及与虚拟线程的设计不好兼容的没必要的同步队列。

3. 其实可以考虑 Java 重构 synchronized 不 pin 线程,但是不知道要什么时候了。

2. 非抢占设计与切换消耗不适合 CPU 密集计算型任务:

(1)非抢占式设计:虚拟线程只会在遇到阻塞的时候与底层平台线程分离切换,否则不会切换。比如你有 4 核,同时启动 8 个平台线程的计算任务,每个任务基本上进度是一样的。同时启动 8 个虚拟线程的计算任务,则是先执行 4 个,之后再执行 4 个。

(2)虚拟线程切换的消耗比较大,虽然已经做了很多优化(Continuation 的堆栈增量复制,按需复制,优化虚拟线程 GC 根引用扫描),但是消耗还是很大,下面是一个平台线程执行与虚拟线程执行计算任务的 CPU 采样对比:

8e221a4845dae1a4c68184b78e83818e.jpeg
8e221a4845dae1a4c68184b78e83818e.jpeg
fd0640a288dbfae68c056c9a0fe208ac.jpeg
fd0640a288dbfae68c056c9a0fe208ac.jpeg
3. ThreadLocal很常用很难去掉,ScopedLocal 不能解决所有问题

第一个问题是内存占用太大导致吞吐量下降:ThreadLocal 虽然底层的 Map 是 WeakReference 的,但是设计之初是考虑 Thread 数量有限。在有虚拟线程很大量的时候,这个 Map 是非常消耗内存的。ScopedValue 通过限制作用域,以及值不可变的方式,优化了内存占用的问题。但是,ScopedValue 还处于预览阶段,并且没有解决 ThreadLocal 的所有问题

没有解决的问题就是第二个问题:之前有很多使用 ThreadLocal 作为资源池的场景(很多库都这么用)。比如说,最早的线程不安全的 SimpleDateFormat(虽然现在已经不怎么用了)。它解决线程不安全的方式,就是或者每次新建一个 SimpleDateFormat,或者使用 ThreadLocal<SimpleDateFormat> 针对每个线程创建一个独立的。后者肯定消耗比前者小。但是,引入了虚拟线程,就相当于回到了最原来的做法。针对这种资源池的场景(即限制某个线程不安全的资源,每个平台线程创建一个独立使用不并发就行了),其实我们还是想对于平台线程创建。这个对于很多库的影响很多,比如 jackson,jackson 针对这个问题的解决方式是:https://github.com/FasterXML/jackson-core/pull/1064/files#diff-0d3d4113de19d16bfce8a0fffa471b3f90096602b45d598eca91c6b226f7cf2d

其实也不是说用新的 ThreadLocal 去替换,而是换种思路,将对象池化的同时,让程序从池子里获取并在用完的时候放回。相当于明确控制生命周期。但是我们也可以看出,这个对于三方库的改造,也是很大的

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-05-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. pin 线程引发的问题比预期严重,需要修改的库繁多
  • 2. 非抢占设计与切换消耗不适合 CPU 密集计算型任务:
  • 3. ThreadLocal很常用很难去掉,ScopedLocal 不能解决所有问题
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档