繁体   English   中英

java NIO内部是怎么工作的,内部使用的是线程池吗?

[英]How does java NIO work internally, is thread pool used internally?

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

jvm 是否有实际执行同步 IO 的线程池? 有对 Linux 的本机 AIO 支持 - java 是否在内部使用它。 AIO 如何在操作系统级别工作 - 它是否有线程池但在操作系统级别 - 或者有一些根本不需要线程的魔法?

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

内核本身(无论是windows或linux还是更具异国情调的东西)负责进行非阻塞I / O,而nio包中的java类(如Channel和Selector)只是该API的低级翻译。

低级别的东西要求你创建线程才能正确完成。 java。*本身的基本NIO支持允许你调用一个阻塞的方法,直到你感兴趣的至少一件事发生在任意数量的批处理非阻塞通道上。 例如,您可以拥有1000个代表网络套接字的开放通道,所有这些通道都在等待“如果某些网络数据包到达这些1000个开放套接字中的任何一个,我感兴趣”,然后调用方法说:“请睡觉直到发生有趣的事情” 。 如果你设置你的应用程序来调用这个方法,然后处理所有有趣的事情,然后回到调用这个方法,你写了一个相当低效的应用程序:CPU往往有多个核心,除了一个以外都是睡着了绝对没事。 正确的模型是拥有多个线程(每个核心或多或少一个)所有运行相同的'唤醒我有一个有趣的事物列表'模型。 除非你故意执行错误的代码,否则你无法摆脱线程。

所以,假设你说你已经正确设置了:你有一个8核CPU,你有8个线程运行'wait-for-interesting-stuff,handle-socket-with-active-data'循环。

想象一下你的句柄套接字代码块的一部分。 也就是说,它会做一些会导致CPU检查其他工作的事情,因为它必须等待网络或磁盘等等。 让我们说因为你已经在那里放了一些数据库查询,你没有意识到数据库查询使用(可能是本地的,但仍然是)网络并点击磁盘。 这真的很糟糕:你有足够的CPU资源来处理这1000个传入的请求,但你的整个8个线程都在等待数据库执行操作,而CPU可能正在分析数据包和响应,它有没有什么可做的,并且等待数据库从磁盘获取记录所花费的时间。

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

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

最后,有表现: 为什么你想摆脱线程?

线程受到2次重大处罚:

  1. 上下文切换。 当CPU必须跳转到另一个线程时(因为它所在的线程需要等待磁盘或网络数据,因此现在无需做任何事情),它需要跳转到另一个代码位置并找出要加载的内存表进入缓存来运行它。

  2. 堆栈。 就像几乎每个编程模型一样,有一些称为“堆栈”的内存,它包含局部变量和调用它的方法的位置(以及调用它的方法,一直到主方法/线程运行)方法)。 如果你得到一个堆栈跟踪,你会看到它的效果。 在java中,每个线程获得1个堆栈,并且所有堆栈的大小相同。 您可以使用-Xss JVM参数对其进行配置,最小值为1MB。 这意味着,如果你想同时拥有4000个线程,那就是4GB的堆栈并且这是无法避免的(然后你需要更多的内存用于堆等等)。

但是,非阻塞对于这些问题中的任何一个都没有那么多解决方案:

  1. 当移动到另一个处理程序因为你没有要处理的数据时,你...也是上下文切换。 它不是一个线程切换,但你仍然需要跳到一个完全不同的内存页面,而在现代架构中,访问一部分不在缓存中的内存需要很长时间。 你只是在'内存页面缓存上下文切换'的'线程上下文切换'进行交易,你什么都没得到。

  2. 假设您是某种聊天应用程序,并且您已从其中一个连接的客户端收到要发送的消息。 您现在需要查询数据库,以查看此用户是否有权将此消息发布到要将其发送到的聊天频道,还可以查看是否还有其他需要更新的跟随模式设备。 因为这是一个阻止操作,你想在等待时跳到另一个工作。 但是你需要在某个地方记住这个状态:发送用户,消息,数据库查询的结果。 在线程模型中,这些数据会自动和隐式地为您处理:它位于该堆栈空间中。 如果您使用完整的NIO,则需要自行管理,例如使用ByteBuffers。

是的,当您手动控制字节缓冲区时,您可以使它们精确到它们所需的大小,并且通常远小于1MB,因此您可以通过这种方式处理更多的同时连接。 或者,您只需在服务器中丢弃一块64GB的RAM。

那么实用的结果是这样的:

  1. NIO代码非常难以编写。 使用像灰熊或网络这样的抽象,因为它是火箭科学。

  2. 它很快就会更快。

  3. 如果需要为连接/文件/作业/等跟踪的数据量较少, 可以同时执行更多操作。

  4. 这有点像使用汇编程序而不是C语言,因为你可以从技术上挤出更多的性能,而不是让java为你做垃圾收集。 但是有一个原因是大多数人不使用汇编程序来编写内容,即使它在理论上更快。 绝大多数Web应用程序都是用java,python或node.js或其他高级程序编写的,而不是像C(++)或汇编程序那样的非托管语言。

问题“Java NIO如何在内部工作?” 对于StackOverflow而言过于宽泛,但关于线程池的问题却没有。

我创建了一个名为SimpleNet的网络框架 ,我想用它来回答你的问题,因为它使用了AsynchronousServerSocketChannelAsynchronousSocketChannel等类。

executor = new ThreadPoolExecutor(numThreads, numThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), runnable -> {
    Thread thread = new Thread(runnable);
    thread.setDaemon(false);
    return thread;
});

executor.prestartAllCoreThreads();

channel = AsynchronousServerSocketChannel.open(AsynchronousChannelGroup.withThreadPool(executor));

在上面从我的项目中获取的代码片段中,您可以看到AsynchronousServerSocketChannel#open接受AsynchronousChannelGroup ,您可以在其中传递自定义ThreadPoolExecutor (这是一个ExecutorService )。

所以回答你的问题:是的,即使使用Asynchronous* NIO类,线程池也用于处理I / O完成。

注意 :一旦Project Loom完成并且Fibers已经占领了全世界,这可能会改变。

我会推测它是如何工作的,但我不确定。 我会假设当.network 卡读取一个帧时,它会将帧写入专用于该硬件的系统定义的 RAM 部分,并向 CPU 发出中断请求。 CPU 会注意到出现了一个中断,并将运行为该中断分配的例程,该例程是网络驱动程序的软件。 驱动程序将读取 memory 并将其转换为操作系统 API 想要的格式。 现在操作系统读取 OSI model 中的帧和所有帧,并在 memory 中组织每个连接的数据。然后操作系统提供 API 来访问这些数据。 对于 windows,API 称为重叠的 IO。现在 JVM 将使用操作系统的 API 来确定是否有可用数据。 操作系统通知可用数据的方式取决于操作系统设计者的设计方式。 一个非常常见的实现是一个名为 select 的阻塞 function,您可以在其中提供 sockets 的列表,如果您对读取可用性或写入可用性感兴趣,如果 sockets 中的任何一个有事件,则 select 会解除阻塞。 一旦解除阻塞,数据就会写入/读取到 ByteBuffer,然后您的完成处理程序将由专门生成的线程调用,以调用完成处理程序。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM