从操作系统内核看Java非阻塞IO事件检测

简介: 非阻塞服务器模型最重要的一个特点是,在调用读取或写入接口后立即返回,而不会进入阻塞状态。在探讨单线程非阻塞IO模型前必须要先了解非阻塞情况下Socket事件的检测机制,因为对于非阻塞模式最重要的事情是检测哪些连接有感兴趣的事件发生,一般会有如下三种检测方式。

非阻塞服务器模型最重要的一个特点是,在调用读取或写入接口后立即返回,而不会进入阻塞状态。在探讨单线程非阻塞IO模型前必须要先了解非阻塞情况下Socket事件的检测机制,因为对于非阻塞模式最重要的事情是检测哪些连接有感兴趣的事件发生,一般会有如下三种检测方式。

应用程序遍历socket检测

如图所示,当多个客户端向服务器请求时,服务器端会保存一个socket连接列表,应用层线程对socket列表进行轮询尝试读取或写入。对于读取操作,如果成功读取到若干数据则对读取到的数据进行处理,读取失败则下个循环再继续尝试;对于写入操作,先尝试将数据写入指定的某个socket,写入失败则下个循环再继续尝试。

这里写图片描述

这样看来,不管多少个socket连接都可以被一条线程管理起来,一条线程负责遍历这些socket列表,不断地尝试读取或写入数据,很好地利用了阻塞的时间,处理能力得到提升。但这种模型需要在应用程序中遍历所有的socket列表,同时需要处理数据的拼接,连接空闲时可能也会占用较多CPU资源,不适合实际使用。对此改进是使用事件驱动的非阻塞方式。

内核遍历socket的事件检测

这种方式将socket的遍历工作交给了操作系统内核,对socket遍历的结果组织成一系列的事件列表并返回应用层处理。对于应用层,他们需要处理的对象就是这些事件,这就是其中一种事件驱动的非阻塞方式的实现。

如图所示,服务器端有多个客户端连接,应用层向内核请求读写事件列表。内核遍历所有socket并生成对应的可读列表readList和可写列表writeList,readList标明了每个socket是否可读,例如socket1的值为1,表示可读,socket2的值为0,表示不可读。writeList则标明了每个socket是否可写。应用层遍历读写事件列表readList和writeList,做相应的读写操作。

这里写图片描述

内核遍历socket的方式已经不用在应用层对所有socket进行遍历,将遍历工作下移到内核层,这种方式有助于提高检测效率。但是它需要将所有连接的可读事件列表和可写事件列表传到应用层,假如socket连接数量大起来的话,列表从内核复制到应用层也是不小的开销。另外,当活跃连接较少时,内核与应用层之间存在很多无效的数据拷贝,因为它是将活跃和不活跃的连接状态都拷贝到应用层了。

内核基于回调的事件检测

通过遍历的方式检测socket是否可读可写是一种效率比较低的方式,不管是在应用层遍历还是在内核遍历。所以需要另外一种机制来优化遍历的方式,那就是回调函数。内核中socket都对应一个回调函数,当客户端往socket发送数据时,内核从网卡接收数据后就会调回调函数将此socket作为可读事件加入到事件列表中。应用层获取此事件列表即可得到所有感兴趣的事件。

如图所示,服务器端有多个客户端socket连接,首先应用层告诉内核每个socket感兴趣的事件;接着当客户端发送数据过来时,对应会有一个回调函数,内核从网卡复制数据成功后即调回调函数将socket1作为可读事件event1加入到事件列表,同样地,内核发现网卡可写时就将socket2作为可写事件event2添加到事件列表中;最后,应用层向内核请求读写事件列表,内核将包含了event1和event2的事件列表返回应用层,应用层通过遍历事件列表得知socket1有数据待读取,于是进行读操作,而socket2则可以写入数据。

这里写图片描述

这种方式由操作系统内核维护客户端的所有连接并通过回调函数不断更新可读可写事件列表,而应用层线程只要遍历这些事件列表即可知道可读取或可写入的连接,进而对这些连接进行读写操作。极大提高了检测效率,自然处理能力也更强。

上面介绍了三种非阻塞的事件检测机制,对于Java来说,非阻塞IO的实现完全是基于操作系统内核的非阻塞IO,它将操作系统的非阻塞IO的差异屏蔽了并提供统一的API,让我们不必关心操作系统。JDK会帮我们选择非阻塞IO的实现方式,例如对于Linux系统,在支持epoll的情况下JDK会优先选择用epoll实现Java的非阻塞IO。这种非阻塞方式的事件检测机制就是效率最高的“内核基于回调的事件检测”。

========广告时间========

鄙人的新书《Tomcat内核设计剖析》已经在京东销售了,有需要的朋友可以到 https://item.jd.com/12185360.html 进行预定。感谢各位朋友。

为什么写《Tomcat内核设计剖析》

=========================

目录
相关文章
|
1月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
2月前
|
Java 大数据
解析Java中的NIO与传统IO的区别与应用
解析Java中的NIO与传统IO的区别与应用
|
2月前
|
存储 算法 Java
Java面试题:深入探究Java内存模型与垃圾回收机制,解释JVM中堆内存和栈内存的主要区别,谈谈对Java垃圾回收机制的理解,Java中的内存泄漏及其产生原因,如何检测和解决内存泄漏问题
Java面试题:深入探究Java内存模型与垃圾回收机制,解释JVM中堆内存和栈内存的主要区别,谈谈对Java垃圾回收机制的理解,Java中的内存泄漏及其产生原因,如何检测和解决内存泄漏问题
48 0
|
1天前
|
Java 大数据 API
Java 流(Stream)、文件(File)和IO的区别
Java中的流(Stream)、文件(File)和输入/输出(I/O)是处理数据的关键概念。`File`类用于基本文件操作,如创建、删除和检查文件;流则提供了数据读写的抽象机制,适用于文件、内存和网络等多种数据源;I/O涵盖更广泛的输入输出操作,包括文件I/O、网络通信等,并支持异常处理和缓冲等功能。实际开发中,这三者常结合使用,以实现高效的数据处理。例如,`File`用于管理文件路径,`Stream`用于读写数据,I/O则处理复杂的输入输出需求。
|
22天前
|
消息中间件 算法 Java
深入浅出操作系统:进程管理的艺术掌握Java中的异常处理机制
【8月更文挑战第30天】在数字世界的舞台上,操作系统扮演着导演的角色,精心安排着每一个进程的表演。本文将揭开进程管理的神秘面纱,从进程的诞生到终结,探究它们如何在操作系统的指挥下和谐共舞。通过生动的比喻和直观的代码示例,我们将一同走进操作系统的核心,理解进程调度、同步与通信的内在机制,以及它们对计算生态的重要性。让我们跟随代码的节奏,一起感受操作系统的魅力吧!
|
25天前
|
监控 算法 安全
Java并发编程案例分析:死锁的检测与解决
Java并发编程案例分析:死锁的检测与解决
21 2
|
30天前
|
Java 数据处理
Java IO 接口(Input)究竟隐藏着怎样的神秘用法?快来一探究竟,解锁高效编程新境界!
【8月更文挑战第22天】Java的输入输出(IO)操作至关重要,它支持从多种来源读取数据,如文件、网络等。常用输入流包括`FileInputStream`,适用于按字节读取文件;结合`BufferedInputStream`可提升读取效率。此外,通过`Socket`和相关输入流,还能实现网络数据读取。合理选用这些流能有效支持程序的数据处理需求。
25 2
|
1月前
|
XML 存储 JSON
【IO面试题 六】、 除了Java自带的序列化之外,你还了解哪些序列化工具?
除了Java自带的序列化,常见的序列化工具还包括JSON(如jackson、gson、fastjson)、Protobuf、Thrift和Avro,各具特点,适用于不同的应用场景和性能需求。
|
1月前
|
缓存 Java
【IO面试题 一】、介绍一下Java中的IO流
Java中的IO流是对数据输入输出操作的抽象,分为输入流和输出流,字节流和字符流,节点流和处理流,提供了多种类支持不同数据源和操作,如文件流、数组流、管道流、字符串流、缓冲流、转换流、对象流、打印流、推回输入流和数据流等。
【IO面试题 一】、介绍一下Java中的IO流
|
1月前
|
网络协议 Java
JAVA实现心跳检测【长连接】
这篇文章介绍了Java中实现心跳检测机制的方法,包括心跳机制的简介、实现方式、客户端和服务端的代码实现,以及具体的测试结果。文中详细阐述了如何通过自定义心跳包和超时检测来维持长连接,并提供了完整的客户端和服务端示例代码。
JAVA实现心跳检测【长连接】