gpt4 book ai didi

java - java NIO内部是如何工作的,内部是否使用线程池?

转载 作者:行者123 更新时间:2023-12-01 09:59:55 24 4
gpt4 key购买 nike

Nio 提供了异步 io——意味着调用线程不会被 IO 操作阻塞。但是,我仍然很困惑这在内部是如何工作的?
从这个答案 - 只有提交同步 IO 的线程池。

jvm 是否具有实际执行同步 IO 的线程池?
Linux 有 native AIO 支持 - java 是否在内部使用它。 AIO 如何在 OS 级别上工作——它是否有线程池但在 OS 级别上——或者有一些根本不需要线程的魔力?

一般来说,问题是 - 异步 NIO 是否让我们能够获取线程 - 或者它只是同步 IO 的包装器,允许我们有固定数量的线程来执行 IO

最佳答案

内核本身(无论是 windows 或 linux 还是更奇特的东西)负责进行非阻塞 I/O,而 nio 包中的 java 类(例如 Channel 和 Selector)只是该 API 的相当低级的翻译.

低级的东西需要你制作线程才能正确地做。 java.* 中的基本 NIO 支持本身允许您调用一个方法,该方法会阻塞,直到您感兴趣的至少一件事情发生在任意数量的批处理非阻塞 channel 上。例如,您可以有 1000 个表示网络套接字的开放 channel ,它们都在等待“如果某些网络数据包到达这 1000 个开放套接字中的任何一个,我很感兴趣”,然后调用一个方法说:“请 sleep 直到发生有趣的事情” .如果你设置你的应用程序来调用这个方法,然后处理所有有趣的事情,然后回到调用这个方法,你就编写了一个相当低效的应用程序:CPU 往往有多个内核,除了一个内核之外,其他所有内核都处于 hibernate 状态什么都不做。正确的模型是让多个线程(每个内核或多或少一个)都运行相同的“用一系列有趣的事情唤醒我”模型。除非您故意编写性能不佳的代码,否则您无法摆脱线程。

因此,假设您已经正确设置了它:您有一个 8 核 CPU,并且有 8 个线程运行“等待有趣的东西,处理套接字和 Activity 数据”循环。

想象一下句柄套接字代码块的一部分。也就是说,它会做一些会导致 CPU 去检查其他作业要做的事情,因为它必须等待,比如说,网络,或磁盘,或类似的东西。假设您已经在其中放置了一些数据库查询,但您没有意识到数据库查询使用(可能是本地的,但仍然是)网络并访问磁盘。这真的很糟糕:您有足够的 CPU 资源来处理那 1000 个传入请求,但是您的整个 8 个线程集都在等待 DB 执行操作,而 CPU 可以分析数据包和响应,它有没有什么可做的,并且会减少等待数据库从磁盘获取记录所需的时间。

坏的。所以,不要调用阻塞代码。不幸的是,Java 中有大量方法(在 Java 核心库和第三方库中)会阻塞。他们往往没有被记录在案。对此没有真正的解决方案。

一些图书馆确实提供了解决方案,但如果他们这样做,则必须采用“回调”形式:以数据库查询为例:您必须做的是获取该网络套接字,至少告诉它您是现在,不再对传入数据感兴趣(您已经在等待 DB 响应,尝试为此套接字处理更多传入数据毫无意义);相反,您想要关联(并且 NIO api 本身不支持此功能,您必须构建某种框架)数据库连接本身,因为“如果此数据库查询已准备好响应,我很感兴趣”。 Java 作为一种语言不适合以这种方式编写,您最终会遇到“回调 hell ”,这就是 javascript 的工作方式。有回调 hell 的解决方案,但它仍然很复杂,java基本上不支持它们(例如,'yield'是一个可以帮助的东西。Java不支持yield概念)。

最后,还有性能:为什么要摆脱线程?

线程会导致 2 个主要惩罚:

  • 上下文切换。当 CPU 必须跳转到另一个线程时(因为它所在的线程需要等待磁盘或网络数据,因此现在无事可做),它需要跳转到另一个代码位置并找出要加载的内存表进入缓存运行它。
  • 堆栈。就像几乎所有的编程模型一样,有一点称为“堆栈”的内存,其中包含局部变量和调用您的方法的位置(以及调用它的方法,一直到您的主方法/线程运行)方法)。如果您获得堆栈跟踪,您正在查看它的效果。在java中,每个线程有1个栈,所有栈的大小相同。您可以使用 -Xss 配置它JVM 参数,最小值为 1MB。意思是,如果你想要同时有 4000 个线程,那是 4GB 的堆栈,这是不可避免的(然后你需要更多的堆内存等等)。

  • 但是,非阻塞并不能很好地解决这两个问题:
  • 当由于要处理的数据用完而移动到另一个处理程序时,您……还进行了上下文切换。这不是线程切换,但您仍然需要跳转到完全不同的内存页面,并且在现代架构中,访问不在缓存中的部分内存需要很长时间。您只是将“线程上下文切换”换成“内存页面缓存上下文切换”,而您一无所获。
  • 假设您是某种聊天应用程序,并且您已从连接的客户端之一收到要发送的消息。您现在需要查询数据库以查看此用户是否有权将此消息发布到它打算将其发送到的聊天 channel ,并查看是否还有任何其他需要更新的跟随模式设备。因为这是一个阻塞操作,你想在等待时跳到另一个工作。但是你需要在某个地方记住这个状态:发送用户、消息、数据库查询的结果。在线程模型中,这些数据会自动且隐式地为您处理:它位于该堆栈空间中。如果你使用完整的 NIO,你需要自己管理它,例如使用 ByteBuffers。

  • 是的,当您手动控制字节缓冲区时,您可以将它们精确地设置为所需的大小,并且通常远小于 1MB,因此您可以通过这种方式处理更多的并发连接。或者,您只需在服务器中放入 64GB 内存条。

    那么,务实的结果是:
  • NIO 代码极难编写。使用 grizzly 或 netty 之类的抽象,因为它是火箭科学。
  • 它很少更快。
  • 如果需要为连接/文件/作业/等跟踪的数据量很少,您可以同时进行更多的事情。
  • 这有点像使用汇编程序而不是 C,因为从技术上讲,您可以通过手动进行垃圾收集而不是让 java 为您完成更多的性能。但是大多数人不使用汇编程序来编程是有原因的,即使它在理论上更快。绝大多数 Web 应用程序都是用 java、python、node.js 或其他高级语言编写的,而不是像 C(++) 或汇编程序这样的非托管语言,这是有原因的。
  • 关于java - java NIO内部是如何工作的,内部是否使用线程池?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54483857/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com