一、Java有哪些IO方式
传统的java.Io包,基于流模型实现,提供了我们最熟悉的一些功能,包括File抽象,输入输出流等。交互方式是同步、阻塞的方式。比如在读取输入流时,在输入流就绪之前,线程会一直阻塞在哪里。它们之间的调用是可靠的线性调用。java.Io包的优点是比较简单,缺点是IO效率和扩展性存在局限性,容易成为应用性能瓶颈。
在Java1.4中引入了NIO框架(Java.nio包),提供了Selector、channel、buffer等抽象。可以构建同步、非阻塞的应用程序。同时提供了更接近操作系统底层的数据操作方式。
在Java7中,NIO有了进一步的改进,也就是NIO2,引入了异步非阻塞IO方式,也有许多人叫它AIO(Asnychronous IO)。异步IO引入了事件和回调机制。可以理解为,应用操作直接返回,而不会阻塞在哪里,当后台处理完成,操作系统会通知相应线程进行后续工作。
二、NIO的主要组成部分
- Buffer,高效的数据容器,处理Boolean,其他原始数据类型都提供了相应Buffer实现
- channel,类似于Linux系统上的文件描述符,是NIO被用来支持批量式IO操作的一种抽象。File和Socket,可以被认为是一种高层次的抽象,而channel是一种更底层的抽象。可以利用操作系统的底层机制,获得特定场景的性能优化。
- Selector,是NIO实现多路复用的基础。它提供了一种机制,可以检测到注册在Selector上的多个channel是否有channel处于就绪状态,进而实现了单个线程对多个channel的处理,即多路复用。Selector依赖于操作系统的底层实现,在Linux系统上,Selector依赖于epoll函数。
三、NIO能解决什么问题
对于一个应用服务器,当连接数较少的时候,可以使用BIO+线程池模型来处理IO问题。而且这个模型比较简单,也不同过多考虑系统的过载,限流问题。但是这个模型的问题在于严重依赖于线程,需要多线程来处理多任务,而线程是很贵的资源。主要体现在:
- 线程的创建和销毁成本很高,在Linux系统中线程本质是一个进程,创建和销毁都需要调用重量级的函数
- 线程本身占用较大内存,像Java的线程栈,一般都会分配512kb-1M的内存空间,如果线程过千,整个JVM的内存都会被吃掉一大半
- 线程的切换成本很高。线程切换时操作系统首先保存线程上下文,然后执行系统调用,如果线程数过高,可能导致线程切换时间大于线程执行时间,导致系统几乎陷入不可用的状态。
当面对数十万甚至上百万的连接是,传统的BIO是无能为力的,这时候就需要用到一种更高效的IO模型。NIO提供了多路复用机制,为解决问题提供了一种思路。
四、NIO使用步骤
- 首先,通过Selector.open()创建一个Selector,作为类似调度员的角色。
- 然后,创建一个ServerSocketChannel,并且向Selector注册。
- Selector阻塞在select操作,当有channel发生接入请求,就会被唤醒
- 通过ServerSocketChannel和Buffer,进行数据操作
可以看到,NIO利用了单线程轮询事件的机制,通过高效地定位就绪的channel,来决定做什么,仅仅select阶段是阻塞的(轮询阶段没有可干的事情必须要阻塞)。可以有效避免大量客户端连接时线程的频繁切换问题,应用的扩展性有了非常大的提高。
五、Proactor与Reactor
一般情况下,IO复用机制需要用到时间分发器(event dispatcher)。事件分发器的作用,就是把事件源分给时事件的处理者。就像送快递的在楼下喊:谁谁谁的快递到了,块来拿吧。开发人员在开始的时候需要在分发器哪里注册感兴趣的时间,并提供相应的处理者(event handler),或者是回调函数。事件分发器在适当的时候,会将请求的事件分发该处理者或回调函数。
事件分发器的两种模式成为Reactor和Proactor。Reactor模式是基于同步IO的,而Proactor模式是和异步IO相关的。在Reactor模式下,事件分发器等待某个事件可用或某个操作的状态发生,事件分发器就把这个事件传给实现注册的事件处理器或回调函数,由后者来做事件的读写操作。而在Proactor模式中,事件处理者直接发起一个异步读写操作,而实际的工作是由操作系统来完成的。时间分发器得知了这个请求,它默默等待请求的完成,然后转发完成事件给时间处理器或回调函数。
六、NIO带来的好处
- 时间驱动模型
- 避免多线程
- 单线程处理多任务
- 非阻塞IO,IO读写不在阻塞,而是返回0
- 基于block的传输,通常比基于流的传输更高效
- 更高级的IO函数,zero-copy
- IO多路复用大大提高了Java网络应用的实用性和可伸缩性
七、NIO的缺陷
- 使用NIO!=高性能,并发程度不高或局域网环境下NIO并没有显著的性能优势
- NIO并没有完全屏蔽平台差异,它依然是基于各个操作系统的IO实现的,差异仍然存在
- 推荐大家使用成熟的NIO框架,如Netty,解决了很多NIO的缺陷,并屏蔽了操作系统的差异,有较好性能和编程模型
- NIO不适合数据量太大交互的场景。对于多路复用IO,当有新的IO请求出现在数据拷贝阶段,如果拷贝数据量很大会导致线程长期阻塞,最后造成性能瓶颈的情况
八、BIO、NIO、AIO使用场景
- BIO方式适用于连接数目比较少且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序比较简单
- NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持
- AIO方式适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持