怎样的Java NIO工作在内部,内部使用线程池?

时间:2019-02-01 16:54:15

标签: java io nio

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

jvm是否具有实际执行同步IO的线程池? Linux有本机AIO支持-Java是否在内部使用它。 AIO是如何在OS级别上工作的-它是否具有线程池,但在OS级别上-还是有些魔术根本不需要线程?

一般而言,问题是-做异步NIO可以使我们获得线程绑定的能力吗?或者,它只是围绕同步IO进行包装,从而使我们可以有固定数量的线程来执行IO

2 个答案:

答案 0 :(得分:1)

内核本身(将它Windows或Linux或更多的东西外来)负责做非阻塞I / O,并在NIO包的Java类(诸如信道,和选择器)仅是相当低的电平转换该API的内容。

低级内容要求您创建线程才能正确执行。基本NIO在Java中支持。*本身可以让你调用一个方法,该方法阻塞,直到至少1件事,你有兴趣发生任何数量的批量在一起无阻塞通道。例如,您可能有1000个代表网络套接字的开放通道都在等待“我是否对某些网络数据包到达这1000个开放套接字中的任何一个感兴趣”,然后调用方法说:“请休眠直到发生有趣的事情” 。如果您设置的应用程序调用此方法,然后处理所有有趣的东西,并返回到调用这个方法,你写一个相当低效的应用:的CPU往往已经远远超过一个核心,并且所有,但一个都睡着了即使什么也不做。正确的模型是让多个线程(每个内核或多或少)都运行相同的“用有趣的事情清单唤醒我”模型。除非故意使代码执行不当,否则您将无法摆脱线程。

所以,让我们假设说你将它设置正确:您有一个8核CPU,你有8个线程运行的“等待换有趣-东西,手柄插口,与活性数据”循环。

设想你的手柄插座码块的一部分。也就是说,它执行某些操作会导致CPU去检查其他作业,因为它必须等待网络或磁盘等。假设是因为您在其中放置了一些数据库查询,但您没有意识到数据库查询使用了(可能是本地,但仍然是)网络连接并击中了磁盘。这会是非常糟糕:你的CPU资源丰富地处理那些1000个传入的请求,但你的8个线程的整组都在等待的DB做的东西,而CPU可以对包进行分析和响应,它有没有任何事情要做,并且减少了等待数据库从磁盘获取记录所需的时间。

不好。因此,请 NOT 调用阻止代码。不幸的是,Java中有很多方法(包括Java核心库和第三方库)会阻塞。他们往往不被记录在案。对此没有真正的解决方案。

某些库确实提供了解决方案,但是如果提供了解决方案,则必须采用“回调”形式:以数据库查询为例:您需要做的就是获取该网络套接字,告诉它您正在,至少到目前为止,不再对传入数据感兴趣(您已经在等待数据库响应,试图为此套接字处理更多传入数据没有任何意义);相反,你要联想(和NIO API不支持此本身,你就必须建立某种框架)的DB连接本身为“我有兴趣,如果这个数据库查询有准备的响应”。 Java作为一种语言不适合采用这种方式编写,最终会出现“回调地狱”,这是javascript的工作方式。有回调地狱解决方案,但它仍然是复杂的,和java基本上不支持(例如,“产量”是一个东西,可以帮助。Java不支持收益率的概念)。

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

线程招致2个主要​​罚分:

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

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

但是,无阻塞没有太大的溶液,为任一这些问题:

  1. 当由于没有足够的数据来处理而移至另一个处理程序时,您还将进行上下文切换。这不是一个线程切换,但你仍然需要跳到内存完全不同的页面,并在现代建筑,访问内存中的一部分,是不是在高速缓存需要很长的时间。您只是用“线程上下文切换”代替了“内存页面缓存上下文切换”,却一无所获。

  2. 假设您是某种聊天应用程序,并且您已经从一个连接的客户端中收到一条要发送的消息。现在,您需要查询数据库,以查看该用户是否有权将此消息发布到它打算发送到的聊天通道,还需要查看是否还有其他需要更新的跟随模式设备。因为那是你想跳到另一份工作,而你等待阻塞操作。但是,你要记住这个国家的某个地方:发送用户的消息,你的数据库查询的结果。在线程模型,这个数据是自动地,隐式照顾你:这是在堆栈空间。如果您要使用完整的NIO,则需要自己进行管理,例如使用ByteBuffers。

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

那么,务实的结果是这样:

  1. NIO代码极难编写。使用诸如灰熊或Netty之类的抽象概念,因为它是火箭科学。

  2. 很少会更快。

  3. 如果 需要跟踪的连接/文件/作业/等数据量很低,您可以同时进行更多操作。

  4. 这是一个有点像用汇编代替C,因为技术上可以挤出更多表现出来的手工做垃圾回收,而不是让Java的为您代劳。但还有一个原因,大多数人不使用汇编程序的东西,即使它在理论上是更快。有绝大多数的网络应用程序的被Java编写的,或Python或node.js中,或别的东西高电平的原因,而不是像C(++)或汇编非托管语言

答案 1 :(得分:0)

“ Java NIO如何在内部工作?”问题对于StackOverflow来说太宽泛了,但是关于线程池的问题却不是。

我创建了一个名为SimpleNet的网络 框架,我想以此为例来回答您的问题,因为它利用了AsynchronousServerSocketChannel,{{ 1}}等。

AsynchronousSocketChannel

在上面从我的项目中摘录的代码片段中,您可以看到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 NIO类,也使用线程池来处理I / O完成。

注意:该项目可能会在Project Loom完成并且Fibre接管世界之后改变。