Java NIO

来源:互联网 发布:js使用aes加密 编辑:程序博客网 时间:2024/06/05 18:20

一、NIO与AIO的区别

首先来讲一下传统的IO与NIO的区别,传统的IO又称为BIO,即阻塞式IO,NIO就是非阻塞式IO。

PS:阻塞与非阻塞是针对于线程在访问数据的时候,根据IO操作的就绪状态来采取不同的方式,说白了就是一种读取或者写入操作函数的实现方式,Java IO的各种流失阻塞的,这就意味着在阻塞方式下当一个线程调用read()或write()时,该线程一直会被阻塞,直到有一些数据被读取或者数据被完全写入,该线程在此期间不能再干其他任何事情;Java NIO的非阻塞方式下,使得一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变得可以读取之前,该线程依旧可以继续做其他的事情,非阻塞方式下写入数据也是如此。线程通常将非阻塞IO的空闲时间用在其他通道上执行IO操作,所以一个单独的线程现在可以管理多个输入输出通道(Channel)。

我们知道阻塞IO在调用InputStream.read()方法时是阻塞的,它会一直等到数据到来时(或超时)才会返回;同样,在调用ServerSocker.accept()方法时,也会一直阻塞直到有客户端连接时才会返回,每一个客户端连接过来后,服务器端都会启动一个线程去处理该客户端的请求。如下图所示:


我们可以发现:

1)当有大量的客户端需要连接时,我们将要创建大量的线程来处理(线程并不是越多越好,创建大量的线程反而会影响服务器的性能)

2)阻塞可能带来频繁的上下文切换,这种上下文切换可能是无意义的

PS:为什么说线程多了反而不好?

①线程的创建与销毁都有一定的资源消耗

②系统线程数量过多,如果线程之间的上下文切换总时间大于线程运行的时间,那么本末倒置

③每一个线程的创建,都要占用大量的栈内存和CPU时间

④线程数量过多导致系统负载过大,多线程有可能同时从阻塞状态到事件发生时同时被唤醒,接收CPU的调度,一瞬间系统的负载达到上限

二、Java NIO原理

Java NIO是在JDK1.4开始使用的,JDK1.4之后提供了一套专门的API专门操作非阻塞IO

NIO API主要由四部分组成:缓冲区Buffer,通道Channel,选择器Selector,核心组成类

缓冲区Buffer

缓冲区的工作与通道紧密联系。通道是 I/O 传输发生时通过的入口,而缓冲区是这些数据传输的来源或目标。对于离开缓冲区的传输,您想传递出去的数据被置于一个缓冲区,被传送到通道。对于传回缓冲区的传输,一个通道将数据放置在您所提供的缓冲区中。这种在协同对象(通常是您所写的对象以及一到多个 Channel 对象)之间进行的缓冲区数据传递是高效数据处理的关键。

Buffer的常见方法如下所示:

  • flip(): 写模式转换成读模式
  • rewind():将 position 重置为 0 ,一般用于重复读。
  • clear() :清空 buffer ,准备再次被写入 (position 变成 0 , limit 变成 capacity) 。
  • compact(): 将未读取的数据拷贝到 buffer 的头部位。
  • mark(): reset():mark 可以标记一个位置, reset 可以重置到该位置。
  • Buffer 常见类型: ByteBuffer 、 MappedByteBuffer 、 CharBuffer 、 DoubleBuffer 、 FloatBuffer 、 IntBuffer 、 LongBuffer 、 ShortBuffer 。
通道Channel

通道是一种途径,借助该途径,可以用最小的总开销来访问操作系统本身的IO服务。缓冲区则是通道内部用来发送和接收数据的断电。通道channel充当连接IO服务的导管。通道可以是单向也可以是双向的。一个channel类可能实现定义read( )方法的 ReadableByteChannel 接口,而另一个 channel 类也许实现 WritableByteChannel 接口以提供 write( )方法。实现这两种接口其中之一的类都是单向的,只能在一个方向上传输数据。如果一个类同时实现这两个接口,那么它是双向的,可以双向传输数据。

选择器Selector

选择器提供选择执行已经就绪的任务的能力,这使得多元IO成为可能,就绪选择和多元执行使得单线程能够有效率的同时管理多个IO通道(Channels),简单言之就是selector充当一个监听者,你需要将之前创建的一个或多个可选择的通道注册到选择器对象中。当你调用一个选择器对象的select()方法时,一个表示通道和选择器的键会被更新,用来检查所有被注册到该选择器的通道。你可以获取一个键的集合,从而就可以找到当时已经就绪的通道。通过遍历这些键,你可以选择出每个从上次你调用select()开始直到现在已经就绪的通道。

选择器属性

1) 选择器(Selector)

选择器类管理着一个被注册的通道集合的信息和它们的就绪状态。通道是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态。当这么做的时候,可以选择将被激发的线程挂起,直到有就绪的通道。

2)可选择通道(SelectableChannel)

SeectableChannel可以被注册到Selector对象上,同时可以指定对那个选择器而言,那种操作是感兴趣的。一个通道可以被注册到多个选择器上,但对每个选择器而言只能被注册一次。

 3)选择键(SelectionKey)

选择键封装了特定的通道与特定的选择器的注册关系。选择键对象被SelectableChannel.register()返回并提供一个表示这种注册关系的标记。选择键包含了两个比特集(以整数形式进行编码),指示了该注册关系所关心的通道操作,以及已经准备好的操作。 

键对象表示了一种特定的注册关系。当应该终结这种关系的时候,可以调用SelectionKey对象的cancel()方法。可以通过调用isVaild()方法来检查它是否仍然表示一种有效的关系。当键被取消时,它将被放在相关的选择器的已取消的键的集合里。注册不会立即被取消,但键会立即失效。当再次调用select()方法时(或者一个正在进行的select()调用结束时),已取消的键的集合中的被取消的键将被清理掉,并且相应的注销也将完成。通道会被注销,而新的SelectionKey将被返回。




让我们一起来看看Java NIO的工作原理:

NIO中非阻塞I/O采用了基于Reactor模式的工作方式,I/O调用不会被阻塞,相反是注册感兴趣的特定I/O事件,如可读数据到达,新的套接字连接等等,在发生特定事件时,系统再通知我们。NIO中实现非阻塞I/O的核心对象就是Selector,Selector就是注册各种I/O事件地 方,而且当那些事件发生时,就是这个对象告诉我们所发生的事件。

使用NIO中非阻塞I/O编写服务器处理程序,大体上可以分为下面三个步骤:

1. 向Selector对象注册感兴趣的事件 
2. 从Selector中获取感兴趣的事件 
3. 根据不同的事件进行相应的处理



原创粉丝点击