文章目录
- 1.什么是NIO?
- 2.为什么用NIO,传统IO有什么缺陷?
- 3.NIO和IO的区别
- 4.怎么理解NIO是面向块的、非阻塞的
- 5.NIO是怎么实现的?
1.什么是NIO?
java.nio全称java non-blocking IO(实际上是 new io),是指JDK 1.4 及以上版本里提供的新api(New IO) ,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络。
2.为什么用NIO,传统IO有什么缺陷?
一个使用传统阻塞I/O的系统,如果还是使用传统的一个请求对应一个线程这种模式,一旦有高并发的大量请求,就会有如下问题:
1、线程不够用, 就算使用了线程池复用线程也无济于事;
2、阻塞I/O模式下,会有大量的线程被阻塞,一直在等待数据,这个时候的线程被挂起,只能干等,CPU利用率很低,换句话说,系统的吞吐量差;
3、如果网络I/O堵塞或者有网络抖动或者网络故障等,线程的阻塞时间可能很长。整个系统也变的不可靠
最直接的体现在于服务器与客户端进行通讯时,每加入一台客户端需要一个IO线程阻塞等待对方数据传送,会导致服务器不断开启线程,但这些线程大部分时间都是阻塞在那里,浪费资源,并且支持不了大并发。
3.NIO和IO的区别
原有的 IO 是面向流的、阻塞的,NIO 则是面向块的、非阻塞的。
原始的IO是面向流的,不存在缓存的概念。Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区
Java IO的各种流是阻塞的,这意味着当一个线程调用read或 write方法时,该线程被阻塞,直到有一些数据被读取,或数据完全写入,该线程在此期间不能再干任何事情了。
4.怎么理解NIO是面向块的、非阻塞的
NIO是面向缓冲区的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性。
Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。
通俗理解:NIO是可以做到用一个线程来处理多个操作的。假设有10000个请求过来,根据实际情况,可以分配50或者100个线程来处理。不像之前的阻塞IO那样,非得分配10000个。
5.NIO是怎么实现的?
1.buffer 缓存数组
缓冲区本质上是一个可以写入数据的内存块,然后可以再次读取,该对象提供了一组方法,可以更轻松地使用内存块,使用缓冲区读取和写入数据通常遵循以下四个步骤:
- 写数据到缓冲区;
- 调用buffer.flip()方法;
- 从缓冲区中读取数据;
- 调用buffer.clear()或buffer.compat()方法;
当向buffer写入数据时,buffer会记录下写了多少数据,一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式,在读模式下可以读取之前写入到buffer的所有数据,一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。
三个重要概念(参数)
a.容量(capacity)
b.界限(limit)
c.位置(position)
缓冲区常用的操作
向缓冲区写数据:
- 从Channel写到Buffer;
- 通过Buffer的put方法写到Buffer中;
从缓冲区读取数据: - 从Buffer中读取数据到Channel;
- 通过Buffer的get方法从Buffer中读取数据;
flip方法:
将Buffer从写模式切换到读模式,将position值重置为0,limit的值设置为之前position的值;
clear方法 vs compact方法:
clear方法清空缓冲区;compact方法只会清空已读取的数据,而还未读取的数据继续保存在Buffer中;
2.channel
a. 通道可以同时进行读写,而流只能读或者只能写
b. 通道可以实现异步读写数据
c. 通道可以从缓冲读数据,也可以写数据到缓冲:
channel提供了一个map()方法,可以直接将数据映射到内存中。
3.Selector(选择器)
可以检测多个NIO channel,看看读或者写事件是否就绪。
多个Channel以事件的方式可以注册到同一个Selector,从而达到用一个线程处理多个请求成为可能。
selector的创建
通过调用Selector.open()方法创建一个Selector对象
注册Channel到Selector
channel.configureBlocking(false);
channel.register(selector, Selectionkey.OP_READ);
注:Channel必须是非阻塞的。
所以FileChannel不适用Selector,因为FileChannel不能切换为非阻塞模式,更准确的来说是因为FileChannel没有继承SelectableChannel。Socket channel可以正常使用。
register() 方法的第二个参数。这是一个“ interest集合 ”,意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件:
- Connect
- Accept
- Read
- Write
SelectionKey介绍
一个SelectionKey键表示了一个特定的通道对象和一个特定的选择器对象之间的注册关系。
key.attachment(); //返回SelectionKey的attachment,attachment可以在注册channel的时候指定。
key.channel(); // 返回该SelectionKey对应的channel。
key.selector(); // 返回该SelectionKey对应的Selector。
key.interestOps(); //返回代表需要Selector监控的IO操作的bit mask
key.readyOps(); // 返回一个bit mask,代表在相应channel上可以进行的IO操作。
理解:
传统IO在读写过程中是不允许停止的需要一直占用线程,对于一个一直有内容可读的文件不用切换线程这样效率的确不错,但是对于网络通讯中网络传输的并发需要不停开启新的线程并且对于计算机而言,它的大部分时间都是阻塞的,所以对于这种情况我们就对它进行了改写使用NIO非阻塞式的IO。
非阻塞式的IO,将内容写到一个Buffer中通过操作Bufer的标识(或者说游标),来改变其读或者写的状态,将它变成了分块的模式,通过通道channel进行数据传递,对内存中内容进行映射,读完了就结束,不需要一直占用线程。然而对于channel要有标识需要知道每个channel对应的谁写的谁读的问题,于是就有了Selector,通过regiser注册每个channel这样就可以知道哪个channel读好了,写完了的过程,执行其他的操作,每个channel不是单独的占用线程.这样就更好的支持了并发。