BIO,NIO,AIO 总结
BIO,NIO,AIO 总结
一、同步阻塞 (BIO)
同步阻塞IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器就需要启动一个线程进行处理,如果这个连接不
做任何事情会造成不必要的线程开销,可以通过线程池机制来改善,BIO方式适用于连接数目比较小且固定的架构,这种方式对
服务端资源要求比较高,并发局限于应用中,在JDK1.4以前时唯一的io
二、同步非阻塞(NIO)
同步非阻塞IO,服务器实现模式为一个请求一个线程,即客户端发送的来凝结请求都会注册到多路复用器上,多路复用器轮询到
连接有IO请求时才启动一个线程进行出处理,NIO方式适用于连接数目多且连接比较短的架构,比如连天服务器,并发局限于应用
中,编程比较复杂,JDK1.4开始
三、异步非阻塞(AIO)
异步非阻塞IO,服务器实现模式为一个有效请求一个线程,客户端的IO请求都是由操作系统先完成了再通知服务器用其启动线程
进行处理。AIO方式使用于连接数目多且连接比较长的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK1.7
开始支持
举例说明(区别)
AIO做法是,每个水壶上装一个开关,当水开了以后会提醒对应的线程去处理
NIO的做法是,叫一个线程不停的循环观察每一个水壶,根据每个水壶当前的状态去处理
BIO的做法是,叫一个线程停在一个水壶那,知道这个水壶烧开,才处理下一个水壶
同步与异步
同步:同步就是发起一个调用后,被调用者未处理完请求之前,调用不返回
异步:异步就是发起一个调用后,立刻得到被调用者的回应表示已接受到请求,但是被调用者并没有返回结果,此时我们可以通过处理其他的请求,被调用者通常依靠事件,回调等机制来通知调用者其返回结果
阻塞和非阻塞
阻塞:阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就寻才能继续
非阻塞:非阻塞就是发起一个请求,调用者不用一直等待结果返回,可以先干其他事情
那么同步阻塞、同步非阻塞和异步非阻塞又代表什么意思呢
举个生活中的例子,你妈妈让你烧水,小时候你比较笨,在哪里傻等着水开(同步阻塞),等你长大一点你知道每次烧水的时候你可以先去干点其他的事情,然后只需要时不时的来看下水开了没有(同步非阻塞)。后来,你们家用上了水开了会发出声音的水壶,这样你只需要听到响声就知道水开了,这期间你可以去干其他的事情(异步非阻塞)
1. BIO (Blocking I/O)
同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。
1.1 传统 BIO
BIO通信(一请求一应答)模型图如下(图源网络,原出处不明):
采用BIO通信模型的服务器,通常由一个独立的Acceptor线程负责监听客户端的连接。我们一般通过在while(true)循环中服务端会被调用accept()方法等待接收客户端的连接的方式监听请求,请求一旦接收到一个连接请求,只能等待同当前连接的客户端的操作执行完成,不过可以通过多线程来支持多个客户端的连接,如上图所示
如果要让BIO通信模型能够同时处理多个客户端的请求,就必须使用多线程(主要原因是socket.accept()/socket.read()、socket.write()设计的三个主要函数都是同步阻塞的),也就是说它在接收端奥客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁,这就是典型的--请求-一应答通信模型。我们可以设想一下如果这个链接不做任何事情的haul就会造成不必要的线程开销,不过可以通过线程池机制改善,线程池还可以让线程的创建和回收成本相对较低。使用FixedThreadPool可以有效的控制了线程的最大数量,抱着了系统有限的资源的控制。实现了N(客户端请求数量):M(处理客户端请求的线程数量)的伪异步I/O模型(N可以远远大于M),下面一节“伪异步BIO”中会详细介绍到。
我们再设想一下当客户端并发访问量增加后这种模型会出现什么问题?
在 Java 虚拟机中,线程是宝贵的资源,线程的创建和销毁成本很高,除此之外,线程的切换成本也是很高的。尤其在 Linux 这样的操作系统中,线程本质上就是一个进程,创建和销毁线程都是重量级的系统函数。如果并发访问量增加会导致线程数急剧膨胀可能会导致线程堆栈溢出、创建新线程失败等问题,最终导致进程宕机或者僵死,不能对外提供服务
1.2 伪异步 IO
采用线程池和任务队列可以实现一种叫做伪异步的 I/O 通信框架,它的模型图如上图所示。当有新的客户端接入时,将客户端的 Socket 封装成一个Task(该任务实现java.lang.Runnable接口)投递到后端的线程池中进行处理,JDK 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。
伪异步I/O通信框架采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。不过因为它的底层任然是同步阻塞的BIO模型,因此无法从根本上解决问题。
1.3 代码示例
客户端
public class IOClient { public static void main(String[] args) { // TODO 创建多个线程,模拟多个客户端连接服务端 new Thread(() -> { try { Socket socket = new Socket("127.0.0.1", 3333); while (true) { try { socket.getOutputStream().write((new Date() + ": hello world").getBytes()); Thread.sleep(2000); } catch (Exception e) { } } } catch (IOException e) { } }).start(); } }
服务端
public class IOServer { public static void main(String[] args) throws IOException { // TODO 服务端处理客户端连接请求 ServerSocket serverSocket = new ServerSocket(3333); // 接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理 new Thread(() -> { while (true) { try { // 阻塞方法获取新的连接 Socket socket = serverSocket.accept(); // 每一个新的连接都创建一个线程,负责读取数据 new Thread(() -> { try { int len; byte[] data = new byte[1024]; InputStream inputStream = socket.getInputStream(); // 按字节流方式读取数据 while ((len = inputStream.read(data)) != -1) { System.out.println(new String(data, 0, len)); } } catch (IOException e) { } }).start(); } catch (IOException e) { } } }).start(); } }
1.4 总结
在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
2. NIO (New I/O)
2.1 NIO 简介
NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。
NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 Socket
和 ServerSocket
相对应的 SocketChannel
和 ServerSocketChannel
两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发。
2.2 NIO的特性/NIO与IO区别
如果是在面试中回答这个问题,我觉得首先肯定要从 NIO 流是非阻塞 IO 而 IO 流是阻塞 IO 说起。然后,可以从 NIO 的3个核心组件/特性为 NIO 带来的一些改进来分析。如果,你把这些都回答上了我觉得你对于 NIO 就有了更为深入一点的认识,面试官问到你这个问题,你也能很轻松的回答上来了。
1)Non-blocking IO(非阻塞IO)
IO流是阻塞的,NIO流是不阻塞的。
Java NIO使我们可以进行非阻塞IO操作。比如说,单线程中从通道读取数据到buffer,同时可以继续做别的事情,当数据读取到buffer中后,线程再继续处理数据。写数据也是一样的。另外,非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。
Java IO的各种流是阻塞的。这意味着,当一个线程调用 read()
或 write()
时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了
2)Buffer(缓冲区)
IO 面向流(Stream oriented),而 NIO 面向缓冲区(Buffer oriented)。
Buffer是一个对象,它包含一些要写入或者要读出的数据。在NIO类库中加入Buffer对象,体现了新库与原I/O的一个重要区别。在面向流的I/O中·可以将数据直接写入或者将数据直接读到 Stream 对象中。虽然 Stream 中也有 Buffer 开头的扩展类,但只是流的包装类,还是从流读到缓冲区,而 NIO 却是直接读到 Buffer 中进行操作。
在NIO厍中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的; 在写入数据时,写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。
最常用的缓冲区是 ByteBuffer,一个 ByteBuffer 提供了一组功能用于操作 byte 数组。除了ByteBuffer,还有其他的一些缓冲区,事实上,每一种Java基本类型(除了Boolean类型)都对应有一种缓冲区。
3)Channel (通道)
NIO 通过Channel(通道) 进行读写。
通道是双向的,可读也可写,而流的读写是单向的。无论读写,通道只能和Buffer交互。因为 Buffer,通道可以异步地读写。
4)Selectors(选择器)
NIO有选择器,而IO没有。
选择器用于使用单个线程处理多个通道。因此,它需要较少的线程来处理这些通道。线程之间的切换对于操作系统来说是昂贵的。 因此,为了提高系统效率选择器是有用的。
2.3 NIO 读数据和写数据方式
通常来说NIO中的所有IO都是从 Channel(通道) 开始的。
从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。
从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据。
数据读取和写入操作图示:
2.4 NIO核心组件简单介绍
NIO 包含下面几个核心的组件:
Channel(通道)
Buffer(缓冲区)
Selector(选择器)
整个NIO体系包含的类远远不止这三个,只能说这三个是NIO体系的“核心API”。我们上面已经对这三个概念进行了基本的阐述,这里就不多做解释了。
2.5 代码示例
代码示例出自闪电侠的博客,原地址如下:
https://www.jianshu.com/p/a4e03835921a
客户端 IOClient.java 的代码不变,我们对服务端使用 NIO 进行改造。以下代码较多而且逻辑比较复杂,大家看看就好。
public class NIOServer {
public static void main(String[] args) throws IOException { // 1. serverSelector负责轮询是否有新的连接,服务端监测到新的连接之后,不再创建一个新的线程, // 而是直接将新连接绑定到clientSelector上,这样就不用 IO 模型中 1w 个 while 循环在死等 Selector serverSelector = Selector.open(); // 2. clientSelector负责轮询连接是否有数据可读 Selector clientSelector = Selector.open(); new Thread(() -> { try { // 对应IO编程中服务端启动 ServerSocketChannel listenerChannel = ServerSocketChannel.open();
listenerChannel.socket().bind(new InetSocketAddress(3333))
listenerChannel.configureBlocking(false);
listenerChannel.register(serverSelector, SelectionKey.OP_ACCEPT)
while (true) {
// 监测是否有新的连接,这里的1指的是阻塞的时间为 1ms
if (serverSelector.select(1) > 0) { Set<SelectionKey> set = serverSelector.selectedKeys();
Iterator<SelectionKey> keyIterator = set.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next(); if (key.isAcceptable()) {
try {
// (1) // 每来一个新连接,不需要创建一个线程,而是直接注册到clientSelector SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept();
clientChannel.configureBlocking(false);
clientChannel.register(clientSelector, SelectionKey.OP_READ) } finally {
keyIterator.remove();
}
} catch (IOException ignored) { } }).start(); new Thread(() -> {
try {
while (true) {
// (2) 批量轮询是否有哪些连接有数据可读,这里的1指的是阻塞的时间为 1ms if (clientSelector.select(1) > 0) {
Set<SelectionKey> set = clientSelector.selectedKeys();
Iterator<SelectionKey> keyIterator = set.iterator(); while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isReadable()) {
try { SocketChannel clientChannel = (SocketChannel) key.channel(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); // (3) 面向 Buffer clientChannel.read(byteBuffer); byteBuffer.flip(); System.out.println( Charset.defaultCharset().newDecoder().decode(byteBuffer).toString()); } finally { keyIterator.remove(); key.interestOps(SelectionKey.OP_READ);
} } catch (IOException ignored) { }).start();
}
为什么大家都不愿意用 JDK 原生 NIO 进行开发呢?从上面的代码中大家都可以看出来,是真的难用!除了编程复杂、编程模型难之外,它还有以下让人诟病的问题:
JDK 的 NIO 底层由 epoll 实现,该实现饱受诟病的空轮询 bug 会导致 cpu 飙升 100%
项目庞大之后,自行实现的 NIO 很容易出现各类 bug,维护成本较高,上面这一坨代码我都不能保证没有 bug
Netty 的出现很大程度上改善了 JDK 原生 NIO 所存在的一些让人难以忍受的问题。
3. AIO (Asynchronous I/O)
AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。(除了 AIO 其他的 IO 类型都是同步的,这一点可以从底层IO线程模型解释,推荐一篇文章:《漫话:如何给女朋友解释什么是Linux的五种IO模型?》 )
查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。
参考
《Netty 权威指南》第二版
https://zhuanlan.zhihu.com/p/23488863 (美团技术团队)
一、同步阻塞 (BIO) 同步阻塞IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器就需要启动一个线程进行处理,如果这个连接不 做任何事情会造成不必要的线程开销,可以通过线程池机制来改善,BIO方式适用于连接数目比较小且固定的架构,这种方式对 服务端资源要求比较高,并发局限于应用中,在JDK1.4以前时唯一的io 二、同步非阻塞(NIO) 同步非阻塞IO,服务器实现模式为一个请求一个线程,即客户端发送的来凝结请求都会注册到多路复用器上,多路复用器轮询到 连接有IO请求时才启动一个线程进行出处理,NIO方式适用于连接数目多且连接比较短的架构,比如连天服务器,并发局限于应用 中,编程比较复杂,JDK1.4开始 三、异步非阻塞(AIO) 异步非阻塞IO,服务器实现模式为一个有效请求一个线程,客户端的IO请求都是由操作系统先完成了再通知服务器用其启动线程 进行处理。AIO方式使用于连接数目多且连接比较长的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK1.7 开始支持 四、IO与NIO区别 1、IO面向流,NIO面向缓冲流 2、IO的各种流是阻塞的,NIO是非阻塞模式 3、jaba NIO的选择允许一个单独的线程来监视多个输入通道,可以注册多个通道使用一个选择器,然后使用一个单独的线程来选择 通道。这些通道里已经有可以处理的输入或选择已准备写入的通道,这种选择机制,使得一个单独的线程很容易来管理多个通道 五、同步与异步的区别() 同步:发送一个请求,等待返回,再发送一个请求,同步可以避免出现死锁,脏读的发生 异步:发送一个请求,不等待返回,随时可以再发送一个请求,可以提高效率,保证并发 举例说明(区别) AIO做法是,每个水壶上装一个开关,当水开了以后会提醒对应的线程去处理 NIO的做法是,叫一个线程不停的循环观察每一个水壶,根据每个水壶当前的状态去处理 BIO的做法是,叫一个线程停在一个水壶那,知道这个水壶烧开,才处理下一个水壶 什么是NIO(?) NIO是New I/O的简称,与旧式的基于流的IO方法相对,从名字看,他表示一套java IO标准 >NIO式基于块的,它以块为基本单位处理数据(硬盘上存储的单位也是按Block来存储) >为所有的原始类型提供(BUFFER)缓存支持 >增加通道对象,作为新的原始IO抽象 >支持锁 >提供了基于Selector的异步网络IO
BIO,NIO,AIO 总结的更多相关文章
- (转)也谈BIO | NIO | AIO (Java版)
原文地址: https://my.oschina.net/bluesky0leon/blog/132361 关于BIO | NIO | AIO的讨论一直存在,有时候也很容易让人混淆,就我的理解,给出一 ...
- 拿搬东西来解释udp tcpip bio nio aio aio异步
[群主]雷欧纳德简单理解 tcpip是有通信确认的面对面通信 有打招呼的过程 有建立通道的过程 有保持通道的确认 有具体传输udp是看到对面的人好像在对面等你 就往对面扔东西[群主]雷欧 ...
- 也谈BIO | NIO | AIO (Java版--转)
关于BIO | NIO | AIO的讨论一直存在,有时候也很容易让人混淆,就我的理解,给出一个解释: BIO | NIO | AIO,本身的描述都是在Java语言的基础上的.而描述IO,我们需要从两个 ...
- IO回忆录之怎样过目不忘(BIO/NIO/AIO/Netty)
有热心的网友加我微信,时不时问我一些技术的或者学习技术的问题.有时候我回微信的时候都是半夜了.但是我很乐意解答他们的问题.因为这些年轻人都是很有上进心的,所以在我心里他们就是很优秀的,我愿意多和努力的 ...
- Netty5序章之BIO NIO AIO演变
Netty5序章之BIO NIO AIO演变 Netty是一个提供异步事件驱动的网络应用框架,用以快速开发高性能.高可靠的网络服务器和客户端程序.Netty简化了网络程序的开发,是很多框架和公司都在使 ...
- I/O模型系列之三:IO通信模型BIO NIO AIO
一.传统的BIO 网络编程的基本模型是Client/Server模型,也就是两个进程之间进行相互通信,其中服务端提供位置信息(绑定的IP地址和监听端口),客户端通过连接操作向服务端监听的地址发起连接请 ...
- 【netty】(1)---BIO NIO AIO演变
BIO NIO AIO演变 Netty是一个提供异步事件驱动的网络应用框架,用以快速开发高性能.高可靠的网络服务器和客户端程序.Netty简化了网络程序的开发,是很多框架和公司都在使用的技术. Net ...
- Netty序章之BIO NIO AIO演变
Netty序章之BIO NIO AIO演变 Netty是一个提供异步事件驱动的网络应用框架,用以快速开发高性能.高可靠的网络服务器和客户端程序.Netty简化了网络程序的开发,是很多框架和公司都在使用 ...
- java BIO/NIO/AIO 学习
一.了解Unix网络编程5种I/O模型 1.1.阻塞式I/O模型 阻塞I/O(blocking I/O)模型,进程调用recvfrom,其系统调用直到数据报到达且被拷贝到应用进程的缓冲区中或者发生错误 ...
- BIO | NIO | AIO (Java版)
几篇解释的不错的文章: BIO NIO AIO NIO.2 入门,第 1 部分: 异步通道 API 使用异步 I/O 大大提高应用程序的性能
随机推荐
- host-manager does not exist or is not a readable directory
当tomcat启动出现这个错误时,按照如下步骤可以解决: 1.删掉F:\tomcat20111101\apache-tomcat-6.0.26\conf\Catalina目录下的localhost文件 ...
- GAN 简介
GAN 原理: GAN 的主要灵感来源于博弈论中零和博弈的思想,应用到深度学习神经网络上来说,就是通过生成网络 G(Generator)和判别网络 D(Discriminator)不断博弈,进而使 ...
- JAVA下划线、驼峰相互转换
/** * 下划线转驼峰 * @param str * @return */ public static String lineToHump(String str) { str = str.toLow ...
- Spring实现自定义注解并且配置拦截器进行拦截
有时候我们会自定义注解,并且需要配置拦截器对请求方法含有该自定义注解的方法进行拦截操作 自定义注解类 NeedToken.java import java.lang.annotation.Docume ...
- Buy Tickets(poj2828)
Buy Tickets Time Limit: 4000MS Memory Limit: 65536K Total Submissions: 17416 Accepted: 8646 Desc ...
- 如何让 Spring Security 「少管闲事」
记两种让 Spring Security「少管闲事」的方法. 遇到问题 一个应用对外提供 Rest 接口,接口的访问认证通过 Spring Security OAuth2 控制,token 形式为 J ...
- PowerDotNet平台化软件架构设计与实现系列(11):日志平台
所有后端应用几乎都会记录日志,日志系统可以统一抽象出来提供服务. 最近被Log4j2的安全漏洞刷屏了,作为开发人员的我只能咩哈哈几次表示日志处理太难了,只有折腾过的人才知道这里面的艰辛啊. 在实现Po ...
- CS5211替代PS8625|设计DP转LVDS转接板|替代PS8625方案
1.CS5211与PS8625功能概述 CS5211是一个eDP到LVDS转换器,配置灵活,适用于低成本显示系统.CS5211与eDP 1.2兼容,支持1通道和2通道模式,每通道速度为1.62Gbps ...
- 【jvm】09-full gc分析思路
[jvm]09-full gc分析思路 欢迎关注b站账号/公众号[六边形战士夏宁],一个要把各项指标拉满的男人.该文章已在github目录收录. 屏幕前的大帅比和大漂亮如果有帮助到你的话请顺手点个赞. ...
- JavaScript交互式网页设计笔记 • 【目录】
章节 内容 实践练习 JavaScript交互式网页设计作业目录(作业笔记) 第1章 JavaScript交互式网页设计笔记 • [第1章 JavaScript基本语法] 第2章 JavaScript ...