JavaNIO如何在内部工作,线程池是否在内部使用?

JavaNIO如何在内部工作,线程池是否在内部使用?,java,io,nio,Java,Io,Nio,Nio提供异步io—这意味着调用线程不会在io操作上被阻塞。然而,我仍然不明白这在内部是如何工作的? 从这个答案来看,只有一个线程池提交了同步IO jvm是否有实际执行同步IO的线程池? Linux有本机AIO支持——java是否在内部使用它。AIO是如何在操作系统级工作的?它是否有线程池,但在操作系统级工作?或者有一些神奇的东西,根本不需要线程 一般来说,问题是-do async NIO为我们提供了获取线程装备的能力,或者它只是一个围绕同步IO的包装器,允许我们有固定数量的线程来执行IO对于S

Nio提供异步io—这意味着调用线程不会在io操作上被阻塞。然而,我仍然不明白这在内部是如何工作的? 从这个答案来看,只有一个线程池提交了同步IO

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

一般来说,问题是-do async NIO为我们提供了获取线程装备的能力,或者它只是一个围绕同步IO的包装器,允许我们有固定数量的线程来执行IO

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

我创建了一个名为的网络框架,我想用它作为一个例子来回答您的问题,因为它使用了
AsynchronousServerSocketChannel
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));
executor=new ThreadPoolExecutor(numThreads,numThreads,0L,TimeUnit.ms,new LinkedBlockingQueue(),runnable->{
线程线程=新线程(可运行);
setDaemon(false);
返回线程;
});
executor.prestartalcorethreads();
channel=AsynchronousServerSocketChannel.open(AsynchronousChannelGroup.withThreadPool(执行器));
在上面从我的项目中提取的代码片段中,您可以看到,
AsynchronousServerSocketChannel#open
接受一个
AsynchronousChannelGroup
,您可以在其中传递一个自定义
ThreadPoolExecutor
(这是一个
ExecutorService

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

注意:一旦Project Loom完成并且光纤已经接管世界,这可能会改变。

内核本身(无论是windows还是linux或其他更奇特的)负责执行非阻塞I/O,以及nio包中的java类(如通道和选择器)只是该API的非常低级的翻译

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

所以,让我们假设你已经把它设置好了:你有一个8核CPU,有8个线程运行“等待有趣的东西,处理带活动数据的套接字”循环

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

糟糕。所以,不要调用阻塞代码。不幸的是,java中有很多方法(java核心库和第三方库中都有)会阻塞。它们往往没有记录在案。没有真正的解决办法

有些库确实提供了解决方案,但如果提供了,则必须采用“回调”形式:以DB查询为例:您需要做的是使用网络套接字,告诉它您(至少目前)不再对传入数据感兴趣(您已经在等待数据库响应,尝试处理更多的套接字传入数据是没有意义的);相反,您希望关联(而且NIOAPI本身不支持这一点,您必须构建某种框架)DB连接本身就是“如果这个DB查询已经准备好响应,我很感兴趣”。作为一种语言,Java不适合这样编写,最终会出现“回调地狱”,这就是javascript的工作方式。回调地狱有一些解决方案,但仍然很复杂,Java基本上不支持它们(例如,“yield”是一个有用的概念,Java不支持yield概念)

最后,还有性能问题:为什么要去掉线程

线程会受到两种主要惩罚:

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

  • 堆栈。就像几乎所有编程模型一样,有一个称为“堆栈”的内存位,其中包含局部变量和调用您的方法(以及调用它的方法,一直到主方法/线程运行m)的位置