zoukankan      html  css  js  c++  java
  • 真正理解NIO

    前言

    高并发量引起的问题

    一个使用传统阻塞I/O的系统,如果还是使用传统的一个请求对应一个线程这种模式,一旦有高并发的大量请求,就会有如下问题: 

    1、线程不够用, 就算使用了线程池复用线程也无济于事; 

    2、阻塞I/O模式下,会有大量的线程被阻塞,一直在等待数据,这个时候的线程被挂起,只能干等,CPU利用率很低,换句话说,系统的吞吐量差; 

    3、如果网络I/O堵塞或者有网络抖动或者网络故障等,线程的阻塞时间可能很长。整个系统也变的不可靠;

    什么是NIO

    java.nio全称java non-blocking IO(实际上是 new io),是指JDK 1.4 及以上版本里提供的新api(New IO) ,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络。

    HTTP2.0使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP1.1大了好几个数量级。

    IO和NIO的区别

    原有的 IO 是面向流的、阻塞的,NIO 则是面向块的、非阻塞的。

    怎么理解IO是面向流的、阻塞的

    java1.4以前的io模型,一连接对一个线程。

    原始的IO是面向流的,不存在缓存的概念。Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区

    Java IO的各种流是阻塞的,这意味着当一个线程调用read或 write方法时,该线程被阻塞,直到有一些数据被读取,或数据完全写入,该线程在此期间不能再干任何事情了。

    怎么理解NIO是面向块的、非阻塞的

    NIO是面向缓冲区的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性。

    Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。

    通俗理解:NIO是可以做到用一个线程来处理多个操作的。假设有10000个请求过来,根据实际情况,可以分配50或者100个线程来处理。不像之前的阻塞IO那样,非得分配10000个。



     

    NIO的核心实现

    在标准IO API中,你可以操作字节流和字符流,但在新IO中,你可以操作通道和缓冲,数据总是从通道被读取到缓冲中或者从缓冲写入到通道中。

    NIO核心API :

    1)Buffer:它是包含数据且用于读写的线形表结构(字节数组)。其中还提供了一个特殊类用于内存映射文件的I/O操作。一个 Buffer 实质上是一个容器对象。发送给一个通道的所有对象都必须首先放到缓冲区中;同样地,从通道中读取的任何数据都要读到缓冲区中。

    2)Charset:它提供Unicode字符串影射到字节序列以及逆影射的操作(编码及解码器)。

    3)Channels:通道是对原 I/O 包中的流的模拟。到任何目的地(或来自任何地方)的所有数据都必须通过一个 Channel 对象。包含socket,file和pipe三种管道,它实际上是双向交流的通道(与流的不同之处在)。

    4)Selector:它将多元异步I/O 操作集中到一个或多个线程中
     

    通道Channel

    NIO的通道类似于流,但有些区别如下:

    1. 通道可以同时进行读写,而流只能读或者只能写

    2. 通道可以实现异步读写数据

    3. 通道可以从缓冲读数据,也可以写数据到缓冲: 

    缓存Buffer

    缓冲区本质上是一个可以写入数据的内存块,然后可以再次读取,该对象提供了一组方法,可以更轻松地使用内存块,使用缓冲区读取和写入数据通常遵循以下四个步骤:

    1. 写数据到缓冲区;

    2. 调用buffer.flip()方法;

    3. 从缓冲区中读取数据;

    4. 调用buffer.clear()或buffer.compat()方法;

    当向buffer写入数据时,buffer会记录下写了多少数据,一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式,在读模式下可以读取之前写入到buffer的所有数据,一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。

    Buffer在与Channel交互时,需要一些标志:

    buffer的大小/容量 - Capacity

    作为一个内存块,Buffer有一个固定的大小值,用参数capacity表示。

    信息末尾的位置 - limit  ,上限,缓冲区中第一个不能被读或写的元素,或者说,缓冲区中现存元素的计数。

    当前读/写的位置 - Position​

    当写数据到缓冲时,position表示当前待写入的位置,position最大可为capacity – 1;当从缓冲读取数据时,position表示从当前位置读取。

    Mark   标记,一个备忘位置

    在写模式下,缓冲区的limit表示你最多能往Buffer里写多少数据; 写模式下,limit等于Buffer的capacity,意味着你还能从缓冲区获取多少数据。

    下图展示了buffer中三个关键属性capacity,position以及limit在读写模式中的说明:


     
        public void test(File inFile,File outFile) throws IOException{
            
            // 获取源文件和目标文件的输入输出流
            FileInputStream fis = new FileInputStream(inFile);
            FileOutputStream fos = new FileOutputStream(outFile);
            // 获取输入输出通道
            FileChannel fcIn = fis.getChannel();
            FileChannel fcOut = fos.getChannel();
            // 创建缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024 );
            while (true) {
                // clear 方法重设缓冲区,使它可以接受读入的数据
                buffer.clear();
                // 从输入通道中将数据读到缓冲区
                int r = fcIn.read(buffer);
                //
                if (r==-1) {
                    break;
                }
                // filp 方法让缓冲区可以将读入的数据写入另一个通道
                buffer.flip();
                // 将这些数据从缓冲区写到输出通道
                fcOut.write(buffer);
            }
            
        }

    缓冲区常用的操作

    向缓冲区写数据:

        1. 从Channel写到Buffer;

        2. 通过Buffer的put方法写到Buffer中;

    从缓冲区读取数据:

        1. 从Buffer中读取数据到Channel;

        2. 通过Buffer的get方法从Buffer中读取数据;

    flip方法:

         将Buffer从写模式切换到读模式,将position值重置为0,limit的值设置为之前position的值;

    clear方法 vs compact方法:

           clear方法清空缓冲区;compact方法只会清空已读取的数据,而还未读取的数据继续保存在Buffer中;



    Selector

    一个组件,可以检测多个NIO channel,看看读或者写事件是否就绪。

    多个Channel以事件的方式可以注册到同一个Selector,从而达到用一个线程处理多个请求成为可能。


     


    作者:萧熏儿
    链接:https://www.jianshu.com/p/362b365e1bcc
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    PlayerPrefs存储Vector3等结构数据
    Kafka集群部署及測试
    火云开发课堂
    Thinking in Java:容器深入研究
    求int型数据在内存中存储时1的个数
    JAVA 几种多线程的简单实例 Thread Runnable
    Android利用Intent与其它应用交互
    kernel
    Azure DocumentDB 正式发布
    在公有云平台体验开源方案的自动部署
  • 原文地址:https://www.cnblogs.com/yrjns/p/12906824.html
Copyright © 2011-2022 走看看