Alm*_*zak 18 java concurrency multithreading project-loom
我正在研究Project Loom 的运作方式以及它能为我的公司带来什么样的好处。
\n所以我理解其动机,对于基于标准 servlet 的后端,总是有一个执行业务逻辑的线程池,一旦线程因为 IO 而被阻塞,它除了等待之外什么也做不了。假设我有一个具有单个端点的后端应用程序,该端点背后的业务逻辑是使用 JDBC 读取一些数据,该 JDBC 内部使用 InputStream,后者将再次使用阻塞系统调用(就 Linux 而言,为 read())。因此,如果我有 20000 个用户到达此端点,我需要创建 200 个线程,每个线程等待 IO。
\n现在假设我将线程池切换为使用虚拟线程。根据 Ben Evans 在《深入 Java\xe2\x80\x99s Project Loom 和虚拟线程》一文中的说法:
\n\n\n相反,当进行阻塞调用(例如 I/O)时,虚拟线程会自动放弃(或让出)其承载线程。
\n
据我了解,如果我的操作系统线程数量等于 CPU 核心数量和无限数量的虚拟线程,则所有操作系统线程仍将等待 IO,并且执行程序服务将无法为虚拟分配新工作线程,因为没有可用的线程来执行它。它与常规线程有何不同,至少对于操作系统线程,我可以将其扩展到数千以增加吞吐量。或者我只是误解了 Loom 的用例?提前致谢
\n我刚刚读过这个邮件列表:
\n\n\n虚拟线程喜欢阻塞 I/O。如果线程需要阻塞(例如 Socket 读取),那么这会释放底层内核线程以执行其他工作
\n
我不确定我是否理解它,如果操作系统执行诸如读取之类的阻塞调用,则操作系统无法释放线程,出于这些目的,内核具有非阻塞系统调用,例如 epoll,它不会阻塞线程并立即返回具有一些可用数据的文件描述符列表。上面的引用是否意味着在幕后,如果调用它的线程是虚拟的, JVM 会将阻塞替换read为非阻塞?epoll
Tho*_*ger 20
您的第一个摘录遗漏了重要的一点:
相反,当进行阻塞调用(例如 I/O)时,虚拟线程会自动放弃(或让出)其承载线程。这是由库和运行时处理的[...]
含义是这样的:如果您的代码对库(例如 NIO)进行阻塞调用,则库检测到您从虚拟线程调用它,并将阻塞调用转换为非阻塞调用,停放虚拟线程并继续处理一些其他虚拟线程代码。
仅当没有虚拟线程准备好执行时才会停放本机线程。
请注意,您的代码永远不会调用阻塞系统调用,它会调用 java 库(当前执行阻塞系统调用)。Project Loom 替换了您的代码和阻塞系统调用之间的层,因此可以做任何它想做的事情 - 只要您的调用代码的结果看起来相同。
Alm*_*zak 11
我终于找到了答案。正如我所说,默认情况下,InputStream.read方法会进行read()系统调用,根据 Linux 手册页,该系统调用将阻塞底层操作系统线程。那么 Loom 怎么可能不会阻止它呢?我发现一篇文章显示了堆栈跟踪所以如果这段代码将由虚拟线程执行
URLData getURL(URL url) throws IOException {
try (InputStream in = url.openStream()) {//blocking call
return new URLData(url, in.readAllBytes());
}
}
Run Code Online (Sandbox Code Playgroud)
JVM 运行时会将其转换为以下堆栈跟踪
java.base/jdk.internal.misc.VirtualThreads.park(VirtualThreads.java:60)//this line parks the virtual thread
java.base/sun.nio.ch.NioSocketImpl.park(NioSocketImpl.java:184)
java.base/sun.nio.ch.NioSocketImpl.park(NioSocketImpl.java:212)
java.base/sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:356)//JVM runtime will replace an actual read() into read from java nio package
java.base/java.io.InputStream.readAllBytes(InputStream.java:346)
Run Code Online (Sandbox Code Playgroud)
JVM 如何知道何时取消虚拟线程?readAllBytes这是完成后将运行的堆栈跟踪
"Read-Poller" #16
java.base@17-internal/sun.nio.ch.KQueue.poll(Native Method)
java.base@17-internal/sun.nio.ch.KQueuePoller.poll(KQueuePoller.java:65)
java.base@17-internal/sun.nio.ch.Poller.poll(Poller.java:195)
Run Code Online (Sandbox Code Playgroud)
文章作者使用MacOs,Mac使用kqueue非阻塞syscall,如果我在Linux上运行它,我会看到epollsyscall。
所以基本上 Loom 没有引入任何新东西,在底层它是一个带有epoll回调的普通系统调用,可以使用 Vert.x 等框架来实现,而 Vert.x 在底层使用 Netty,但在 Loom 中,回调逻辑被封装在 JVM 运行时中我发现这与直觉相反,当我调用 InputStream.read() 时,我确实期望相应的 read() 系统调用,但 JVM 会将其替换为非阻塞系统调用。