之前的文章《虚拟线程目前不推荐上生产的个人思考》,总结了几个目前的问题:
1. synchronized 的 pin 线程引发的问题比预期严重,或者等到 OpenJDK 修复,或者很多 Java 库要改(尤其是 JDBC 驱动这种)。目前 monitor enter 的 pin 线程问题在 Loom 的预计合入 OpenJDK 23 的分支上有了初步解决方案。
2. 虚拟线程的调度与之前的线程不一样,有些场景需要注意不适用。
3. ThreadLocal很常用很难去掉,ScopedLocal 不能解决所有问题,比如将 ThreadLocal 用作对象池的避免并发访问的场景,这种其实是想针对底层负载线程创建对象。这种一般需要三方库做修改,比如 jackson,jackson 针对这个问题的解决方式是:https://github.com/FasterXML/jackson-core/pull/1064/files#diff-0d3d4113de19d16bfce8a0fffa471b3f90096602b45d598eca91c6b226f7cf2d
一些在 Java 22 的改进:
1. 虚拟线程底层也是通过 epoll 和 kqueue 实现的非阻塞 io,相应地,肯定有 poller 线程。虚拟线程的负载线程,默认是 FoekJoinPool.common 大小也是可用 CPU 数量 - 1,再加上 poller 线程的争用。同时,如果监听事件的线程和实际的处理的线程是同一个也是更好的。所以 Java 22 也会将 poller 线程变为虚拟线程。经过测试,这个修改的提升是比较大的。
其实虚拟线程除了这些已知的使用问题,还有明确需要 OpenJDK 解决的问题目前还没有明确的解决方案,但应该是在解决中:
1. 只要涉及本地帧(可以理解为 JVM C++/C 层面的调用,系统调用等等),如果是本地帧导致的阻塞,大部分都会 pin 线程。
2. 由于 1 的存在,sychronized 的首要问题解决了,但是之后的 wait notify 还是依赖 JVM 层的队列和代码,还是会 pin 线程。
3. 由于 1 的存在,虚拟线程触发类加载,还是会 pin 线程,因为类加载主要代码也是在 JVM 层做的。
4. 文件 io 的阻塞,linux 平台底层通过 io_uring 改写,但是目前整体的改写方案还没定。