一、引子

nio是java的IO框架里边十分重要的一部分内容,其最核心的就是提供了非阻塞IO的处理方式,最典型的应用场景就是处理网络连接。很多同学提起nio都能说起一二,但是细究其背后的原理、思想往往就开始背书,说来说去都是那么几句,其中不少人并不见的真的很理解。本人之前就属于此类,看了很多书和博客,但是大多数都只是讲了三件套和怎么使用,很少会很细致的讲背后的思想,那本次我们就来扒一扒吧。
    很多博客描述nio都是这么说的:基于Reactor模式实现的多路非阻塞高性能的网络IO。那么我们就从这个定义来分析,其中两个关键点:多路非阻塞和Reactor模式。(本来想把高性能也算进去,但是后来想想这个应该算前两者的结果)下边我们来分别搞懂这两块。

二、网络IO模型

多路非阻塞其实准确的名字叫做IO多路复用模型,其是linux五种网络模型之一,也是当前网络编程最常使用的模型之一。至于详细的介绍请参考博客:高性能IO模型浅析(这个里边只给出了4中,没有信号驱动IO,但讲的很赞,特别是图),这里仅作简要介绍和对比:

  • 阻塞IO:java中老的bio便是这种模式,在接到事件(数据到达、数据拷贝完成等)前程序需阻塞等待。优点是编码简单,缺点是效率低,处理程序阻塞会导致cpu利用率很低。
  • 非阻塞IO:在未接到事件时处理程序一直主动轮询,这样处理程序无需阻塞,可以在轮询间歇去干别的,但是轮询会造成重复请求,同样浪费资源。以前java中实现的的伪异步模式就是采用这种思想。
  • IO复用模型:增加了对socket的事件监听器(selector),从而把处理程序和对应的socket事件解耦,所用的socket连接都注册在监听器,在等待阶段只有监听器会阻塞,处理线程从监听器获取事件对socket连接处理即可,而且一个处理线程可以对应多个连接(前两种一般都是一个socket连接起一个线程,这就是为什么叫复用),有点是节省资源,由于处理程序能够被多个连接复用,因此少数的线程就能处理大量连接。缺点同样因为复用,如果是大量费时处理的连接(如大量连接上传大文件),很容易造成线程占满而导致新连接失败。
  • 信号驱动IO模型:在数据准别阶段无需阻塞,只需向系统注册一个信号,在数据准备好后,系统会响应该信号。该模型依赖于系统实现,而且信号通信使用比较麻烦,因此java中未有对应实现。
  • 异步IO:与信号驱动IO很类似,而且在数据拷贝阶段(指数据从系统缓冲区拷贝至程序自己的缓冲区,其他模型改阶段程序都需要阻塞等待)同样可以异步处理。有点不必多说,效率很高,缺点是依赖系统底层实现。目前很多语言都提供该模型的实现,jdk1.7之后同样在concurrent包中提供了。

对比以上五种模型可以知道,IO复用模型从效率和实现成本综合而言目前是比较好的选择,这就是java基于该模型实现nio的根本原因。上边提到了IO复用模型的实现思想,其实这种思想在其他语言中早已实现(如C++中据说流弊哄哄超10w行代码的ACE,自适配通信环境,就采用了该模型),并且提出了一个叫Reactor的设计模式。

三、Reactor模式

Reactor模式,翻译过来叫做反引器模式,其目的是在事件驱动的应用中,将一个请求的能够分离并且调度给应用程序。我相信大多数人都没看明白前一句的意思(书还是要背的),说白了就是对于一个请求的多个事件(如连接、读写等),经过这种模式的处理,能够区分出来,并且分别交给对应的处理模块处理。废话不多说,来看下一个简图:

    可以看到Reactor模式中组件有acceptor、dispatcher和handler(这里只是拿一种实现做个例子,真实的实现各有不同),其中acceptor中注册了各类事件,当连接有新的事件过来时,其会将事件交给dispatcher进行分发;dispatcher绑定了事件和对应处理程序handler的映射关系,当接到新事件时其会把事件分发到对应handler;而handler负责处理对应事件,这块就是我们的业务层了。
    从该模式我们可以发现,对于acceptor、dispatcher我们往往只需要一个线程作为入口即可,因为其并不会有耗时处理,效率很高,而handler则根据需要起几个线程即可(多数时候使用一个线程池实现),这正是IO复用模型期望的效果。
    下边我们会介绍NIO是如何实现该模式的,在此之前先介绍一下框架,其实除了NIO之外,基于JVM实现的还有其他Reactor框架,正好最近OSC牵头翻译了对应文档,有兴趣的可以看下:Reactor 指南

四、NIO

    NIO的细节就不多讲了,这里只介绍下三件套:
  • channel:管道,可以看做对流的封装,有点像pipe,不过其是全双工的。其好处是屏蔽了底层细节,不用关心流对应的是文件还是网络,也不用关心连接怎么处理的,而且全双工,不用考虑输入流或输出流,你只用使用buffer对其进行读写就行了。
  • buffer:channel的好基友,底层就是个字节数组,不同的是对其进行了封装,不仅提供了对基本类型的支持,而且内部维持了读写位置(postion、limit、capacity、mark等),还提供了便捷的方法(clear、flip)。对channel的读写必须通过buffer。
  • selector:这个不多说了,如果前边认真看基本上就明白干啥的,就是Reactor模式中Acceptor的实现。

再来看个简图吧:

基本上和Reactor能对应上,少了个dispatcher,这是由于jdk本身提供的nio比较基本,dispatcher一般都由我们自己实现,而在我理解中,mina、netty这些框架很重要的一方面也是提供了该部分的实现。

五、一个例子

从《netty权威指南》上抄了个例子以及配图,而且代码没有客户端的,大家可以瞄一眼吧(为什么没有?因为已经快一点了,我不想写了......):
服务器端时序图:

客户端时序图:

服务器端代码:

package com.gj.netty.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;
import java.util.Set; /**
* Created by guojing on 2015/6/7.
*/
public class MultiplexerTimerServer implements Runnable { private Selector selector;
private ServerSocketChannel servChannel;
private volatile boolean stop; public MultiplexerTimerServer(int port) {
try {
selector = Selector.open(); //新建多路复用selector
servChannel = ServerSocketChannel.open(); //新建channel
servChannel.configureBlocking(false); //设置非阻塞
servChannel.socket().bind(new InetSocketAddress(port),1024); //端口、块大小
servChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("TimeServer is start, port:" + port);
} catch (IOException e) {
e.printStackTrace();
} } public void run() {
while (!stop){
try {
selector.select(1000);
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> ketIt = keys.iterator();
SelectionKey key = null;
while (ketIt.hasNext()){
key = ketIt.next();
ketIt.remove();
//处理对应key事件
handler(key);
}
} catch (IOException e) {
e.printStackTrace();
} }
} private void handler(SelectionKey key){
//根据key去除channel做对应处理
}
}

六、最后一点啰嗦

我想如果这会儿还有人记得标题一定会骂我了,丫的十分钟个屁啊,认真看完至少待半个小时。这个我只能说如果你之前已经理解了,那么画个10分钟瞟一眼无所谓的,如果以前没理解,如果本文能让你有了更好的理解,那么花多少时间更无所谓了,要知道懂了java的nio是量的积累,了解了其背后的思想和原理是质的积累。而且,我明明计划半小时写完的,这会已经2个多小时过去了......

java十分钟速懂知识点——NIO的更多相关文章

  1. java十分钟速懂知识点——System类

    上次面试中遇到的一个问题,问到System.out.println()中的out是不是内部类,当时就给问蒙了,直观感觉out应该是System类的一个属性,跟内部类有什么关系?而且之前整理IO部分的时 ...

  2. java十分钟速懂知识点——引用

    一.由健忘症引起的问题 今天闲来没事在日志中瞟见了个OutOfMemoryError错误,不由得想到前一段时间看到一篇面经里问到Java中是否有内存泄露,这个很久以前是留意过的,大体记得内存溢出和内存 ...

  3. 京东数科二面:常见的 IO 模型有哪些?Java 中的 BIO、NIO、AIO 有啥区别?

    IO 模型这块确实挺难理解的,需要太多计算机底层知识.写这篇文章用了挺久,就非常希望能把我所知道的讲出来吧!希望朋友们能有收货!为了写这篇文章,还翻看了一下<UNIX 网络编程>这本书,太 ...

  4. 京东数科面试真题:常见的 IO 模型有哪些?Java 中的 BIO、NIO、AIO 有啥区别?

    本文节选自<Java面试进阶指北 打造个人的技术竞争力> 面试中经常喜欢问的一个问题,因为通过这个问题,面试官可以顺便了解一下你的操作系统的水平. IO 模型这块确实挺难理解的,需要太多计 ...

  5. 知名互联网公司校招 Java 开发岗面试知识点解析

    天之道,损有余而补不足,是故虚胜实,不足胜有余. 本文作者在一年之内参加过多场面试,应聘岗位均为 Java 开发方向.在不断的面试中,分类总结了 Java 开发岗位面试中的一些知识点. 主要包括以下几 ...

  6. Java面试之http知识点(必问)

    Java面试之http知识点(必问)   版权声明:本文为博主原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/q ...

  7. 一文弄懂-BIO,NIO,AIO

    目录 一文弄懂-BIO,NIO,AIO 1. BIO: 同步阻塞IO模型 2. NIO: 同步非阻塞IO模型(多路复用) 3.Epoll函数详解 4.Redis线程模型 5. AIO: 异步非阻塞IO ...

  8. Java线程并发:知识点

    Java线程并发:知识点   发布:一个对象是使它能够被当前范围之外的代码所引用: 常见形式:将对象的的引用存储到公共静态域:非私有方法中返回引用:发布内部类实例,包含引用.   逃逸:在对象尚未准备 ...

  9. 记录这段时间java编程的小知识点

    记录这段时间java编程的小知识点 eclipse项目导入中文乱码 eclipse左侧目录结构变动 eclipse代码段左右移动 按tal键,是整体右移. 按shift  table 同时按,是整体左 ...

随机推荐

  1. 使用create react app教程

    This project was bootstrapped with Create React App. Below you will find some information on how to ...

  2. java Vamei快速教程13 String类

    作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 之前的Java基础系列中讨论了Java最核心的概念,特别是面向对象的基础.在Jav ...

  3. linux 命令——28 tar

    通过SSH访问服务器,难免会要用到压缩,解压缩,打包,解包等,这时候tar命令就是是必不可少的一个功能强大的工具.linux中最流行的tar是麻雀虽小,五脏俱全,功能强大.tar命令可以为linux的 ...

  4. IOS 移除栈顶的控制器

    - (IBAction)back2Two:(id)sender { // 移除当前栈顶的控制器 [self.navigationController popViewControllerAnimated ...

  5. WARNING: The TCP backlog setting of 511.解决

    redis启动警告问题:WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/so ...

  6. EM理解(转)

    EM是我一直想深入学习的算法之一,第一次听说是在NLP课中的HMM那一节,为了解决HMM的参数估计问题,使用了EM算法.在之后的MT中的词对齐中也用到了.在Mitchell的书中也提到EM可以用于贝叶 ...

  7. javascrit中“字符串为什么可以调用成员”

    <script> var title = "this is title"; console.log(title.substr(0,5));   //字符串为什么可以调用 ...

  8. 由一道CTF pwn题深入理解libc2.26中的tcache机制

    本文首发安全客:https://www.anquanke.com/post/id/104760 在刚结束的HITB-XCTF有一道pwn题gundam使用了2.26版本的libc.因为2.26版本中加 ...

  9. 数组逆序=全局内存版 VS 共享内存版

    全局内存版 #include <stdio.h> #include <assert.h> #include "cuda.h" #include " ...

  10. React后台管理系统-table-list组件

    table-list组件可用于商品列表,用户列表页面 需要传入一个tableHeads集合和tablebody import React from 'react';   // 通用的列表 class ...