1.Java IO 演进之路

本文围绕着一下几个问题
1、Java 中 BIO、NIO、AIO 之间的区别及应用场景。
2、阻塞(Block)与非阻塞(Non-Block)区别。
3、同步(Synchronization)和异步(Asynchronous)的区别。
 

1.1 几个概念

1.1.1 阻塞(Block)和非阻塞(Non-Block)

阻塞和非阻塞是进程在访问数据的时候,数据是否准备就绪的一种处理方式,当数据没有准备的时候。
阻塞:往往需要等待缓冲区中的数据准备好过后才处理其他的事情,否则一直等待在那里。
非阻塞:当我们的进程访问我们的数据缓冲区的时候,如果数据没有准备好则直接返回,不会等待。如果数据已经准备好,也直接返回。

1.1.2 同步(Synchronization)和异步(Asynchronous)

同步和异步都是基于应用程序和操作系统处理 IO 事件所采用的方式。比如同步:是应用程序要直接参与 IO 读写的操作。异步:所有的 IO 读写交给操作系统去处理,应用程序只需要等待通知。
同步方式在处理 IO 事件的时候,必须阻塞在某个方法上面等待我们的 IO 事件完成(阻塞 IO 事件或者通过轮询 IO事件的方式),对于异步来说,所有的 IO 读写都交给了操作系统。这个时候,我们可以去做其他的事情,并不需要去完成真正的 IO 操作,当操作完成 IO 后,会给我们的应用程序一个通知。
同步 : 阻塞到 IO 事件,阻塞到 read 或则 write。这个时候我们就完全不能做自己的事情。让读写方法加入到线程里面,然后阻塞线程来实现,对线程的性能开销比较大。

1.2 BIO 与 NIO 对比

下表总结了 Java BIO(Block IO)和 NIO(Non-Block IO)之间的主要差别异。
 
IO模型 BIO NIO
通信 面向流(乡村公路) 面向缓冲(高速公路,多路复用技术)
处理 阻塞 IO(多线程) 非阻塞 IO(反应堆 Reactor)
触发  无  选择器(轮询机制)

 

1.2.1 面向流与面向缓冲

Java NIO 和 BIO 之间第一个最大的区别是,BIO 是面向流的,NIO 是面向缓冲区的。 Java BIO 面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO 的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。

1.2.2 阻塞与非阻塞

Java BIO 的各种流是阻塞的。这意味着,当一个线程调用 read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 Java NIO 的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞 IO 的空闲时间用于在其它通道上执行 IO 操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。

1.2.3 选择器的问世

Java NIO 的选择器(Selector)允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。

1.2.4 NIO 和 BIO 如何影响应用程序的设计

无论您选择 BIO 或 NIO 工具箱,可能会影响您应用程序设计的以下几个方面:
A.对 NIO 或 BIO 类的 API 调用。
B.数据处理逻辑。
C.用来处理数据的线程数。
1.API 调用
当然,使用 NIO 的 API 调用时看起来与使用 BIO 时有所不同,但这并不意外,因为并不是仅从一个 InputStream逐字节读取,而是数据必须先读入缓冲区再处理。
2.数据处理
使用纯粹的 NIO 设计相较 BIO 设计,数据处理也受到影响。在 BIO 设计中,我们从 InputStream 或 Reader 逐字节读取数据。假设你正在处理一基于行的文本数据流,例如:
有如下一段文本:
Name:Tom
Age:18
Email: tom@qq.com
Phone:13888888888
该文本行的流可以这样处理:
FileInputStream input = new FileInputStream("d://info.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
String nameLine = reader.readLine();
String ageLine = reader.readLine();
String emailLine = reader.readLine();
String phoneLine = reader.readLine();
 
请注意处理状态由程序执行多久决定。换句话说,一旦 reader.readLine()方法返回,你就知道肯定文本行就已读完,readline()阻塞直到整行读完,这就是原因。你也知道此行包含名称;同样,第二个 readline()调用返回的时候,你知道这行包含年龄等。 正如你可以看到,该处理程序仅在有新数据读入时运行,并知道每步的数据是什么。一旦正在运行
的线程已处理过读入的某些数据,该线程不会再回退数据(大多如此)。下图也说明了这条原则:
(Java BIO: 从一个阻塞的流中读数据) 而一个 NIO 的实现会有所不同,下面是一个简单的例子:
ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buffer);
注意第二行,从通道读取字节到 ByteBuffer。当这个方法调用返回时,你不知道你所需的所有数据是否在缓冲区内。你所知道的是,该缓冲区包含一些字节,这使得处理有点困难。假设第一次 read(buffer)调用后,读入缓冲区的数据只有半行,例如,“Name:An”,你能处理数据吗?显然不能,需要等待,直到整行数据读入缓存,在此之前,对数据的任何处理毫无意义。所以,你怎么知道是否该缓冲区包含足够的数据可以处理呢?好了,你不知道。发现的方法只能查看缓冲区中的数据。其结果是,在你知道所有数据都在缓冲区里之前,你必须检查几次缓冲区的数据。这不仅效率低下,而且可以使程序设计方案杂乱不堪。例如:
ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buffer);
while(!bufferFull(bytesRead)) {
  bytesRead = inChannel.read(buffer);
}
bufferFull()方法必须跟踪有多少数据读入缓冲区,并返回真或假,这取决于缓冲区是否已满。换句话说,如果缓冲区准备好被处理,那么表示缓冲区满了。
bufferFull()方法扫描缓冲区,但必须保持在 bufferFull()方法被调用之前状态相同。如果没有,下一个读入缓冲区的数据可能无法读到正确的位置。这是不可能的,但却是需要注意的又一问题。如果缓冲区已满,它可以被处理。如果它不满,并且在你的实际案例中有意义,你或许能处理其中的部分数据。但是许多情况下并非如此。下图展示了“缓冲区数据循环就绪”:
3. 设置处理线程数
NIO 可让您只使用一个(或几个)单线程管理多个通道(网络连接或文件),但付出的代价是解析数据可能会比从一个阻塞流中读取数据更复杂。
如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据,例如聊天服务器,实现 NIO 的服务器可能是一个优势。同样,如果你需要维持许多打开的连接到其他计算机上,如 P2P 网络中,使用一个单独的线程来管理你所有出站连接,可能是一个优势。一个线程多个连接的设计方案如:
Java NIO: 单线程管理多个连接
如果你有少量的连接使用非常高的带宽,一次发送大量的数据,也许典型的 IO 服务器实现可能非常契合。下图说明了一个典型的 IO 服务器设计:
Java BIO: 一个典型的 IO 服务器设计- 一个连接通过一个线程处理。

1.4 Java AIO 详解

jdk1.7 (NIO2)才是实现真正的异步 AIO、把 IO 读写操作完全交给操作系统,学习了 linux epoll 模式,下面我们来做一些演示。

1.4.1 AIO(Asynchronous IO)基本原理

服务端:AsynchronousServerSocketChannel
客服端:AsynchronousSocketChannel
用户处理器:CompletionHandler 接口,这个接口实现应用程序向操作系统发起 IO 请求,当完成后处理具体逻辑,否则做自己该做的事情,
“真正”的异步IO需要操作系统更强的支持。在IO多路复用模型中,事件循环将文件句柄的状态事件通知给用户线程,由用户线程自行读取数据、处理数据。而在异步IO模型中,当用户线程收到通知时,数据已经被内核读取完毕,并放在了用户线程指定的缓冲区内,内核在IO完成后通知用户线程直接使用即可。异步IO模型使用了Proactor设计模式实现了这一机制,如下图所示:

1.4.2 AIO 初体验

服务端代码:
    

package com.gupaoedu.vip.netty.io.aio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* AIO 服务端
*/
public class AIOServer {
  private final int port;
  public static void main(String args[]) {
    int port = 8000;
    new AIOServer(port);
  }
  public AIOServer(int port) {
    this.port = port;
    listen();
  }
  private void listen() {
    try {
      ExecutorService executorService = Executors.newCachedThreadPool();
      AsynchronousChannelGroup threadGroup = AsynchronousChannelGroup.withCachedThreadPool(executorService, 1);
      final AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open(threadGroup);
      server.bind(new InetSocketAddress(port));
      System.out.println("服务已启动,监听端口" + port);
      server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>(){
        final ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
        public void completed(AsynchronousSocketChannel result, Object attachment){
          System.out.println("IO 操作成功,开始获取数据");
          try {
            buffer.clear();
            result.read(buffer).get();
            buffer.flip();
            result.write(buffer);
            buffer.flip();
          } catch (Exception e) {
            System.out.println(e.toString());
          } finally {
            try {
              result.close();
              server.accept(null, this);
            } catch (Exception e) {
             System.out.println(e.toString());
           }
         }
         System.out.println("操作完成");
      }
        @Override
        public void failed(Throwable exc, Object attachment) {
          System.out.println("IO 操作是失败: " + exc);
        }
      });
      try {
        Thread.sleep(Integer.MAX_VALUE);
      } catch (InterruptedException ex) {
        System.out.println(ex);
      }
    } catch (IOException e) {
      System.out.println(e);
    }
  }
}
客户端代码:
package com.gupaoedu.vip.netty.io.aio;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
/**
* AIO 客户端
*/
public class AIOClient {
  private final AsynchronousSocketChannel client;
  public AIOClient() throws Exception{
    client = AsynchronousSocketChannel.open();
  }
  public void connect(String host,int port)throws Exception{
    client.connect(new InetSocketAddress(host,port),null,new CompletionHandler<Void,Void>() {
      @Override
      public void completed(Void result, Void attachment) {
        try {
          client.write(ByteBuffer.wrap("这是一条测试数据".getBytes())).get();
          System.out.println("已发送至服务器");
        } catch (Exception ex) {
          ex.printStackTrace();
        }
      }
      @Override
      public void failed(Throwable exc, Void attachment) {
        exc.printStackTrace();
      }
    });
    final ByteBuffer bb = ByteBuffer.allocate(1024);
    client.read(bb, null, new CompletionHandler<Integer,Object>(){
      @Override
      public void completed(Integer result, Object attachment) {
        System.out.println("IO 操作完成" + result);
        System.out.println("获取反馈结果" + new String(bb.array()));
      }
      @Override
      public void failed(Throwable exc, Object attachment) {
        exc.printStackTrace();
      }
    });
    try {
      Thread.sleep(Integer.MAX_VALUE);
    } catch (InterruptedException ex) {
      System.out.println(ex);
    }
  }
  public static void main(String args[])throws Exception{
    new AIOClient().connect("localhost",8000);
  }
}
执行结果:
服务端
客户端

1.5 各 IO 模型对比与总结

最后再来一张表总结
属性 同步阻塞 IO(BIO)  伪异步 IO  非阻塞 IO(NIO) 异步 IO(AIO)
客户端数:IO 线程数  1:1  M:N(M>=N)  M:1  M:0
阻塞类型  阻塞   阻塞  非阻塞 非阻塞
同步  同步  同步  同步(多路复用) 异步
API 使用难度 简单 简单 复杂 一般
调试难度 简单 简单  复杂  复杂
可靠性 非常差
吞吐量
  
 
 

1.IO的演进的更多相关文章

  1. Java NIO 机制分析(一) Java IO的演进

    一.引言 Java1.4之前的早期版本,Java对I/O的支持并不完善,开发人员再开发高性能I/O程序的时候,会面临一些巨大的挑战和困难,主要有以下一些问题: (1)没有数据缓冲区,I/O性能存在问题 ...

  2. java NIO学前准备

    之前一直对NIO感兴趣,无奈对IO的很多概念很模糊,所以对于NIO的学习也是一直半解,最近在网上查阅了很多资料,发现有很多概念是需要反复理解的,有的时候甚至当时理解了,但一段时间后又忘记了,所以决定自 ...

  3. VS2015编译GEOS

    下载链接:http://trac.osgeo.org/geos/ 1. 打开cmake,加载geos源码和定位geos的工程存放位置: 2.点击configure,会报错,首先设置CMAKE_INST ...

  4. Java IO编程全解(一)——Java的I/O演进之路

    转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7419117.html JDK1.4之前的早期版本,Java对I/O的支持并不完善,开发人员在开发高性能I/O ...

  5. 第一章 Java的IO演进之路

    Unix中5种IO模型 就网络通信而言,一次数据读入可以分为两个阶段,首先等待数据从网络中到达,到达后需要复制到内核的缓冲区中,第二个阶段是从内核的缓冲区复制到进程的缓冲区,复制到进程的缓冲区才算读取 ...

  6. IO模型之BIO代码详解及其优化演进

    一.BIO简介 BIO是java1.4之前唯一的IO逻辑,在客户端通过socket向服务端传输数据,服务端监听端口.由于传统IO读数据的时候如果数据没有传达,IO会一直等待输入传入,所以当有请求过来的 ...

  7. netty之IO演进之路

    常见IO类型: 传统的同步阻塞I/O编程<BIO> 基于NIO的非阻塞编程 基于NIO2.0的异步非阻塞AIO编程 BIO缺点: 没有数据缓冲区,I/O性能存在问题 没有Channel概念 ...

  8. 规范化的软件项目演进管理--从 Github 使用说起

    规范化的软件项目演进管理 从 Github 使用说起 1   前言 首先,本文的层次定位是:很基本很基础的 Github 工具的入门级应用,写给入门级的用户看的. 基本上工作过几年的人,下面描述的这些 ...

  9. eventloop & actor模式 & Java线程模型演进 & Netty线程模型 总结

    eventloop的基本概念可以参考:http://www.ruanyifeng.com/blog/2013/10/event_loop.html Eventloop指的是独立于主线程的一条线程,专门 ...

随机推荐

  1. Tkinter 之记事本项目实战

    一.效果图 二.文件 链接:https://pan.baidu.com/s/1CUkfWpfH8t2GSJyy5hYv-A 提取码:5yvn 后续可能做一些功能扩展,有需要源码的可以评论联系我!!!

  2. JavaScript substr() 方法

    定义和用法 substr() 方法可在字符串中抽取从 start 下标开始的指定数目的字符. 语法 stringObject.substr(start,length) 参数 描述 start 必需.要 ...

  3. (大型网站之Nginx)图解正向代理、反向代理、透明代理

    一.正向代理(Forward Proxy) 一般情况下,如果没有特别说明,代理技术默认说的是正向代理技术.关于正向代理的概念如下: 正向代理(forward)是一个位于客户端[用户A]和原始服务器(o ...

  4. 深入理解volatile原理与使用

    volatile:称之为轻量级锁,被volatile修饰的变量,在线程之间是可见的. 可见:一个线程修改了这个变量的值,在另一个线程中能够读取到这个修改后的值. synchronized除了线程之间互 ...

  5. HikariCP 连接最快的连接池

    三点原因 1.字节码精简 2.自定义 FastList 代替ArrayList ;避免每次get()调用都要进行range check,避免调用remove()时的从头到尾的扫描: 3.优化代码和拦截 ...

  6. Oracle语法 及 SQL题目(三)

    目录 SQL题目六 第一个问题思路(查询酒类商品的总点击量) 第二个问题思路(查询每个类别所属商品的总点击量,并按降序排列) 第三个问题思路(查询所有类别中最热门的品种(点击量最高),并按点击量降顺序 ...

  7. 2019软工实践_Alpha(2/6)

    队名:955 组长博客:https://www.cnblogs.com/cclong/p/11862633.html 作业博客:https://edu.cnblogs.com/campus/fzu/S ...

  8. Java利用FastJson一行代码转List<Map>为List<Bean>

    /** * 字符串 -> json对象.实体对象.Map.List.List<Map> */ // 字符串 -> json对象 JSONObject json = JSON.p ...

  9. SelectKBest

    https://www.e-learn.cn/content/python/2198918from sklearn.feature_selection import SelectKBest,f_cla ...

  10. 和重复搭建开发环境说 Bye Bye 之Vagrant

    每每新同事入职,都要在自己电脑上配置一堆环境,费神费力:每每开发测试都要重新配置开发环境,手工搭建,步骤很繁琐,极易出错. 大神在时,大神搭建,大神不在,以手抚膺坐长叹.为此,VVVVVagrant横 ...