我在调查https://wiki.openjdk.java.net/display/loom/Main是如何工作的,它能给我的公司带来什么样的好处。
因此,我理解其动机,对于基于标准servlet的后端,总是有一个线程池执行业务逻辑,一旦线程因为IO而被阻塞,它只能等待。因此,假设我有一个具有单个端点的后端应用程序,这个端点背后的业务逻辑是使用JDBC读取一些数据,它内部使用InputStream,它将再次使用阻塞系统调用( read() )。因此,如果有200多个用户到达此端点,则需要创建每个等待IO的200个线程。
现在,假设我将线程池改为使用虚拟线程。据https://blogs.oracle.com/javamagazine/post/going-inside-javas-project-loom-and-virtual-threads文章中的Ben说
相反,当进行阻塞调用(如I/O)时,虚拟线程会自动放弃(或产生)它们的载波线程。
因此,据我所知,如果OS线程的数量等于CPU内核的数量和无限制的虚拟线程数量,那么所有OS线程仍将等待IO,而Executor服务将无法为虚拟线程分配新的工作,因为没有可用的线程来执行它。它与常规线程有什么不同,至少对于OS线程,我可以将其扩展到上千个,以提高吞吐量。还是我误解了织机的用例?提前感谢
副词
我刚看了这个邮寄名单
虚拟线程喜欢阻塞I/O。如果线程需要阻塞,比如套接字读取,那么这将释放底层内核线程来执行其他工作。
我不确定我是否理解,如果操作系统执行一个阻塞调用(如read ),操作系统就无法释放线程,出于这些目的,内核具有非阻塞系统(如epoll ),它不阻塞线程,并立即返回具有一些可用数据的文件描述符列表。上面的引用是否意味着,如果调用它的线程是虚拟的,JVM将用非阻塞read
替换阻塞epoll
?
发布于 2021-11-30 20:04:22
你的第一个节选遗漏了重要的一点:
相反,当进行阻塞调用(如I/O)时,虚拟线程会自动放弃(或产生)它们的载波线程。这是由库和运行时处理的..。
其含义是:如果代码向库(例如NIO)发出阻塞调用,库将检测到从虚拟线程调用它,并将阻塞调用转换为非阻塞调用,将虚拟线程停住并继续处理其他一些虚拟线程代码。
只有当没有虚拟线程准备执行时,本机线程才会被停放。
请注意,您的代码从不调用阻塞的syscall,它调用java库(当前执行阻塞的syscall)。Project替换代码和阻塞syscall之间的层,因此可以做它想做的任何事情--只要调用代码的结果看起来是一样的。
发布于 2021-11-30 21:40:01
我终于找到了答案。因此,正如我所说的,默认情况下,InputStream.read
方法会生成一个read()
syscall,根据Linux页面,它将阻塞底层的OS线程。那么,织机怎么可能不阻止它呢?我找到了一个显示堆栈跟踪的文章,因此如果这段代码将由虚拟线程执行
URLData getURL(URL url) throws IOException {
try (InputStream in = url.openStream()) {//blocking call
return new URLData(url, in.readAllBytes());
}
}
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)
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)
本文的作者使用MacOs,Mac使用kqueue
作为非阻塞syscall,如果我在Linux上运行它,我会看到epoll
syscall。
所以基本上Loom并没有引入任何新的东西,它是一个普通的epoll
syscall,它带有回调,可以使用一个框架来实现元素,比如Vert.x,它在外壳下使用Netty,但是在Loom中,回调逻辑封装了JVM运行时,我发现这与直觉相反,当我调用InputStream.read()时,我确实期望得到相应的read() syscall,但是JVM将用非阻塞的syscalls来替换它。
发布于 2021-11-30 20:21:35
Thomas Kl ger的答复是正确的。我再加几个想法。
因此,据我所知,如果OS线程的数量等于CPU内核的数量和无限制的虚拟线程数量,那么所有OS线程都将等待IO。
不不对你误会了。
您描述的是在当前Java线程技术下所发生的事情。通过Java线程到主机OS线程的一对一映射,任何用Java发出的阻塞(等待响应的时间相对较长)的调用都会使宿主线程在大拇指上旋转,不做任何工作。如果主机有数以百万计的线程,以便其他线程可以安排在CPU核心上工作,这将不会是一个问题。但是主机操作系统线程是相当昂贵的,所以我们没有一个万万,我们有非常少。
使用Project技术,JVM检测阻塞调用,例如等待I/O。一旦检测到,JVM就会在等待I/O响应时预留(“parks”)虚拟线程。JVM将一个不同的虚拟线程分配给该主机OS载体线程,这样“真实”线程就可以继续执行工作,而不是在旋转它的大拇指时等待。因为JVM中的虚拟线程非常便宜(内存和CPU都非常高效),所以我们可以有成千上万甚至数百万的线程供JVM使用。
在您的示例中,每个线程都在等待IO响应,它们都是JDBC对数据库的调用,如果这些线程都是停在JVM中的虚拟线程的话。被ExecutorService
用作载体线程的少数主机OS线程将用于当前未被阻塞的其他虚拟面包上。这种停车和重新安排阻塞-然后-解除阻塞的虚拟线程是由JVM内的Project技术自动处理的,我们Java应用程序开发人员不需要任何干预。
假设我切换了一个线程池来使用虚拟线程
实际上,没有虚拟线程池。每个虚拟线程都是新鲜的和新的,没有回收。这就消除了对线程局部污染的担忧。
ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor() ;
…
executorService.submit( someTask ) ; // Every task submitted gets assigned to a fresh new virtual thread.
为了了解更多信息,我强烈推荐观看罗恩·普雷斯勒( Ron )或艾伦·贝特曼( Alan )的演示和采访视频,他们是项目织机团队的成员。找到最新的,织机一直在发展。
并阅读新的Java,https://openjdk.java.net/jeps/8277131。
https://stackoverflow.com/questions/70174468
复制相似问题