Bor*_*lov 27 java multithreading project-loom
我对 Loom 项目非常感兴趣,但有一件事我无法完全理解。
\n大多数 Java 服务器使用具有一定线程限制(200、300 ..)的线程池,但是,您不受操作系统的限制可以产生更多线程,我已经读到,通过针对 Linux 的特殊配置,您可以达到巨大的数量。
\n操作系统线程成本更高,启动/停止速度更慢,必须处理上下文切换(按数量放大),并且您依赖于操作系统,而操作系统可能拒绝为您提供更多线程。
\n话虽如此,虚拟线程也消耗类似数量的内存(或者至少我是这么理解的)。使用 Loom,我们可以进行尾部调用优化,这应该会减少内存使用。另外,同步和线程上下文复制应该仍然是一个类似大小的问题。
\n事实上,您可以生成数百万个虚拟线程
\npublic static void main(String[] args) {\n for (int i = 0; i < 1_000_000; i++) {\n Thread.startVirtualThread(() -> {\n try {\n Thread.sleep(1000);\n } catch (Exception e) {\n e.printStackTrace();\n }\n });\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n当我使用平台线程时,上面的代码在 25k 左右中断并出现 OOM 异常。
\n我的问题是,到底是什么让这些线程如此轻量,是什么阻止我们生成 100 万个平台线程并使用它们,是否只是上下文切换使常规线程如此“重”。
\n一个非常相似的问题
\n到目前为止我发现的事情:
\n虚拟线程包装在平台线程上,因此您可能会认为它们是 JVM 提供的幻觉,整个想法是将线程的生命周期设置为CPU 绑定操作。
究竟是什么让 Java 虚拟线程变得更好?
虚拟线程的优点
虚拟线程使用注意事项
不要使用监视器,即同步块,但这将在新版本的 JDK 中修复,替代方法是使用带有 try-final 语句的“ReentrantLock”。
使用堆栈上的本机帧、JNI 进行阻塞。非常罕见
控制每个堆栈的内存(减少线程区域设置并且无深度递归)
监控工具尚未更新,如调试器、JConsole、VisualVM 等
平台线程与虚拟线程。平台线程在基于 IO 的任务和操作中劫持操作系统线程,这些任务和操作仅限于线程池和操作系统线程中适用的线程数量,默认情况下它们是非守护线程
虚拟线程是用 JVM 实现的,在 CPU 绑定操作中关联到平台线程并将它们重新调整到线程池,在 IO 绑定操作完成后,将从线程池中调用一个新线程,因此在这种情况下没有人质。
第四层架构有更好的理解。
中央处理器
操作系统
虚拟机
具有执行器服务的虚拟线程
使用执行器服务更有效,因为它与线程池关联,并且仅限于与其适用的线程,但是与虚拟线程相比,使用执行器服务和虚拟包含的我们不需要处理或管理关联的线程池。
try(ExecutorService service = Executors.newVirtualThreadPerTaskExecutor()) {
service.submit(ExecutorServiceVirtualThread::taskOne);
service.submit(ExecutorServiceVirtualThread::taskTwo);
}
Run Code Online (Sandbox Code Playgroud)
执行器服务在 JDK 19 中实现了 Auto Closable 接口,因此当在“try with resources”中使用时,一旦到达“try”块的末尾,就会调用“close”api,或者主线程将等待所有提交的任务及其专用虚拟线程完成其生命周期,并且关联的线程池被关闭。
ThreadFactory factory = Thread.ofVirtual().name("user thread-", 0).factory();
try(ExecutorService service = Executors.newThreadPerTaskExecutor(factory)) {
service.submit(ExecutorServiceThreadFactory::taskOne);
service.submit(ExecutorServiceThreadFactory::taskTwo);
}
Run Code Online (Sandbox Code Playgroud)
执行器服务也可以使用虚拟线程工厂创建,只需将线程工厂与其构造函数参数放在一起即可。
可以受益于执行器服务的功能,例如 Future 和 Completable Future。
了解有关JEP-425的更多信息
协程(即虚拟线程)的一大优点是它们可以生成高水平的并发性,而没有回调的缺点。
首先介绍一下利特尔定律:
concurrency = arrival_rate * latency
Run Code Online (Sandbox Code Playgroud)
我们可以将其重写为:
arrival_rate = concurrency/latency
Run Code Online (Sandbox Code Playgroud)
在稳定的系统中,到达率等于吞吐量。
throughput = concurrency/latency
Run Code Online (Sandbox Code Playgroud)
要提高吞吐量,您有 2 个选择:
对于常规线程,由于上下文切换开销,很难通过阻塞调用达到高并发水平。在某些情况下可以异步发出请求(例如NIO + Epoll或Netty io_uring绑定),但随后您需要处理回调和回调地狱。
使用虚拟线程,可以异步发出请求并停放虚拟线程并调度另一个虚拟线程。一旦收到响应,虚拟线程就会被重新调度,并且这是完全透明地完成的。该编程模型比使用带回调的经典线程直观得多。
| 归档时间: |
|
| 查看次数: |
5992 次 |
| 最近记录: |