java NIO理解分析与基本使用
我前段时间的一篇博客java网络编程——多线程数据收发并行总结了服务端与客户端之间的收发并行实践。原理很简单,就是针对单一客户端,服务端起两个线程分别负责read和write操作,然后线程保持阻塞等待读写执行。
事实上,这样的模式非常糟糕。因为每一个客户端在服务端需要占用两条线程,假如有1000个客户端,则需要2000+条线程。cpu需要花费大量的时间进行线程上下文切换,造成系统资源浪费。
想要缩减线程数量,先要解决阻塞问题。而NIO可以通过IO多路复用将read和write的阻塞给抹去。再配合线程池,即可实现用少量的线程支撑起上百万个客户端的连接。
什么是NIO
NIO与IO多路复用
java NIO全称java non-blocking IO。字面意思即非阻塞式IO。实际上这里的非阻塞只是宏观的说法。
关于IO模式,这里引一个别人的博客,介绍了几种IO模式的区别:
简述同步IO、异步IO、阻塞IO、非阻塞IO之间的联系与区别
本博客不再赘述这些,只是想说NIO属于其中的IO复用模型。(实验室里有一本《UNIX网络编程》疫情结束回学校一定把这部分好好看看)
多路复用IO模型中,会有一个线程去不断轮询多个socket的状态,当socket有读写事件时,才来调用IO操作。因为是一个线程来管理多个socket,系统不需要建立其它线程、维护线程,只有socket就绪时,才会使用IO资源,所以它大大降低了资源占用。
java NIO中,使用selector.select()监听多个通道是否有到达事件,没有事件就一直阻塞,有事件就调用IO进行处理。
三大核心
- 通道(Channel)
- 缓冲区(Buffer)
- 选择器(Selectors)
详细介绍如下

NIO使用举例
这里以服务端读取客户端消息的流程为例,介绍NIO的使用(完整内容只有输入,暂且不管输出)。画了一个流程图,如下所示:

- 建立selector和ServerSocketChannel,并绑定注册,用于监听客户端连接请求,代码如下:
selector = Selector.open();
ServerSocketChannel server = ServerSocketChannel.open();
// 设置为非阻塞
server.configureBlocking(false);
// 绑定本地端口
server.socket().bind(new InetSocketAddress(port));
// 注册客户端连接到达监听
server.register(selector, SelectionKey.OP_ACCEPT);
同时还要建立readSelector和writeSelector。其实线程池也是提前建立的,这里暂且不写。
readSelector = Selector.open();
writeSelector = Selector.open();
- 监听通道,得到客户端,并建立SocketChannel,用于监听后续客户端消息
//select()方法返回已就绪的通道数
if (selector.select() == 0) {
continue;
}
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
// 检查当前Key的状态是否是accept的
// 客户端到达状态
if (key.isAcceptable()) {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
// 非阻塞状态拿到客户端连接
SocketChannel socketChannel = serverSocketChannel.accept();
try {
// 客户端构建异步线程
// 添加同步处理
//此处代码暂且忽略
} catch (IOException e) {
e.printStackTrace();
System.out.println("客户端连接异常:" + e.getMessage());
}
}
- 将SocketChannel注册进readSelector和writeSelector
/**
*参数分别是:channel,对应的selector,以及
*registerOps:待注册的操作集,这个在后文中有详细解析;
*locker:用于标识同步代码块的状态,是锁定还是可用;
*runnable:执行具体读写操作的类,送给线程池执行;
*map:建立SelectionKey与Runnable映射关系的HashMap。
*/
private static SelectionKey registerSelection(SocketChannel channel, Selector selector,
int registerOps, AtomicBoolean locker,
HashMap<SelectionKey, Runnable> map,
Runnable runnable) {
synchronized (locker) {
// 设置锁定状态
locker.set(true);
try {
// 唤醒当前的selector,让selector不处于select()状态
//注册channel时一定要将selector唤醒,否则当前select中没有刚注册的channel
selector.wakeup();
SelectionKey key = null;
if (channel.isRegistered()) {
// 查询是否已经注册过
key = channel.keyFor(selector);
if (key != null) {
//将新的Ops添加进去
key.interestOps(key.readyOps() | registerOps);
}
}
if (key == null) {
// 注册selector得到Key
key = channel.register(selector, registerOps);
// 注册回调
map.put(key, runnable);
}
return key;
} catch (ClosedChannelException e) {
return null;
} finally {
// 解除锁定状态
locker.set(false);
try {
// 通知
locker.notify();
} catch (Exception ignored) {
}
}
}
}
- 监听各个客户端消息,通过selectionKeys获取channel,再执行输入操作
try {
if (readSelector.select() == 0) {
continue;
}
Set<SelectionKey> selectionKeys = readSelector.selectedKeys();
for (SelectionKey selectionKey : selectionKeys) {
if (selectionKey.isValid()) {
//IO处理代码,暂且忽略
}
}
selectionKeys.clear();
} catch (IOException e) {
e.printStackTrace();
}
注意:以上都是一些代码片段,没有完全串联起来,省略了一些类对象调用、方法调用以及关键的线程池操作等等。但是基本的方法已经展示出来,剩下的后面的博客再去填坑。
光看上面的代码,对于一些NIO方法的认知还是很模糊的。下面通过阅读selector类和SelectionKey类的源码注释,来加深对部分方法的理解。
selector类
selector是NIO的核心类,下面是选择器的一些重要方法:
- open相关
- open()开启一个selector
- public abstract boolean isOpen(); 判断是否开启
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
- keys相关
- public abstract Set keys();返回所有key的集合
- public abstract Set selectedKeys();返回已被选择的key的集合
- select
- 下面几个方法都是返回已就绪通道的数量,可能是0;
- selectNow(),非阻塞方法;
- select(),仅在三种情况下返回,1.通道被选择;2.调用wakeup方法;3.线程中断。
- select(timeout),比select()多一个解除阻塞的条件,即超时。
- wakeup(),解除正在阻塞的select方法的阻塞,立即返回
- close(),关闭selector。
SelectionKey类
注册进selector的任何一个channel都用一个SelectionKey对象来指代。
操作集
- Operation-set:操作集,一些常量int值,代表各种类型的操作;一个selection key包含两个操作集,interest set和ready-operation set
public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;
interest set:兴趣集;一个channel所有的操作集;可通过interestOps(int)方法来更新
ready-operation set:就绪操作集,只包含使得channel被报告就绪的操作,底层通过与或操作来更新;例如当一个channel读取就绪时,将read操作集加入到就绪集中。
方法列表
- public abstract SelectableChannel channel():返回此选择键所关联的通道.即使此key已经被取消,仍然会返回;
- public abstract Selector selector():返回此选择键所关联的选择器,即使此键已经被取消,仍然会返回;
- public abstract boolean isValid():检测此key是否有效.当key被取消,或者通道被关闭,或者selector被关闭,都将导致此key无效.在AbstractSelector.removeKey(key)中,会导致selectionKey被置为无效;
- public abstract void cancel():请求将此键取消注册.一旦返回成功,那么该键就是无效的,被添加到selector的cancelledKeys中.cancel操作将key的valid属性置为false,并执行selector.cancel(key)(即将key加入cancelledkey集合);
- public abstract int interesOps():获得此键的interes集合;
- public abstract SelectionKey interestOps(int ops):将此键的interst设置为指定值.此操作会对ops和channel.validOps进行校验.如果此ops不会当前channel支持,将抛出异常;
- public abstract int readyOps():获取此键上ready操作集合.即在当前通道上已经就绪的事件;
- public final boolean isReadable(): 检测此键"read"事件是否就绪.等效于:(readyOps() & OP_READ) != 0;还有isWritable(),isConnectable(),isAcceptable()
- public final Object attach(Object ob):将给定的对象作为附件添加到此key上.在key有效期间,附件可以在多个ops事件中传递;
- public final Object attachment():获取附件.一个channel的附件,可以再当前Channel(或者说是SelectionKey)生命周期中共享,但是attachment数据不会作为socket数据在网络中传输。
终于写完了,这篇博客只能算是对NIO简单介绍,一些东西还没讲到。channel和buffer部分的方法没有分析,线程池部分没有加上,还有输出操作那一套,都没讲。总想尽可能多地详细完整一点,但是越深入,知识点就越庞大,所以只能放弃一部分内容,于是成了现在这个样子。如果详细规划一下拆开多个博客写会更好。
java NIO理解分析与基本使用的更多相关文章
- Java NIO理解与使用
https://blog.csdn.net/qq_18860653/article/details/53406723 Netty的使用或许我们看着官网user guide还是很容易入门的.因为java ...
- Java NIO原理分析
Java IO 在Client/Server模型中,Server往往需要同时处理大量来自Client的访问请求,因此Server端需采用支持高并发访问的架构.一种简单而又直接的解决方案是“one-th ...
- Java NIO 机制分析(一) Java IO的演进
一.引言 Java1.4之前的早期版本,Java对I/O的支持并不完善,开发人员再开发高性能I/O程序的时候,会面临一些巨大的挑战和困难,主要有以下一些问题: (1)没有数据缓冲区,I/O性能存在问题 ...
- Java nio 理解
Java nio 称为Java new IO ,对Java io而言的.他有两个主要的概念:缓存.通道. 在程序中,数据的来源或写入,要么网络.要么硬盘.所有通道分为:文件通道.TCP通道.UDP通道 ...
- Java - NIO
java.nio:NIO-2: NIO 面向流的IO体系一次只能处理一个或多个字节/字符,直至读取所有字节/符,且流中的数据不能前后移动.效率低,当数据源中没有数据时会阻塞线程.Java-4提供的新A ...
- JAVA IO 以及 NIO 理解
由于Netty,了解了一些异步IO的知识,JAVA里面NIO就是原来的IO的一个补充,本文主要记录下在JAVA中IO的底层实现原理,以及对Zerocopy技术介绍. IO,其实意味着:数据不停地搬入搬 ...
- [JavaEE]Java NIO原理图文分析及代码实现
转http://weixiaolu.iteye.com/blog/1479656 目录: 一.java NIO 和阻塞I/O的区别 1. 阻塞I/O通信模型 2. java NIO ...
- Java NIO原理图文分析及代码实现
原文: http://weixiaolu.iteye.com/blog/1479656 目录: 一.java NIO 和阻塞I/O的区别 1. 阻塞I/O通信模型 2. java ...
- java.nio分析软件包(三)---Charset理解力
前面的分析后,2一个基本的封装类型.现在我们就来揭开Java.nio魔法知识的最后一块,CharsetEncoding类,他的主要功能是实现字节Unicode之间的转换转码. 让我们来看看他同样的封装 ...
随机推荐
- SPA那点事
前端猿一天不学习就没饭吃了,后端猿三天不学习仍旧有白米饭摆于桌前.IT行业的快速发展一直在推动着前端技术栈在不断地更新换代,前端的发展成了互联网时代的一个缩影.而单页面应用的发展给前端猿分了一杯羹. ...
- 前端每日实战:111# 视频演示如何用纯 CSS 创作一只艺术的鸭子
效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/aaoveW 可交互视频 此视频是可 ...
- vue+express+mysql项目总结(node项目部署阿里云通用)
原文发布于我的个人博客上:原文点这里 前面经历千辛万苦,终于把博客的所有东西都准备好了,现在就只等部署了.下面我介绍下我的部署过程: 一.购买服务器和域名 如果需要域名(不用域名通过ip也可以 ...
- 《即时消息技术剖析与实战》学习笔记11——IM系统如何保证服务高可用:流量控制和熔断机制
IM 系统的不可用主要有以下两个原因: 一是无法预测突发流量,即使进行了服务拆分.自动扩容,但流量增长过快时,服务已经不可用了: 二是业务中依赖的这些接口.资源不可用或变慢时,比如发消息可能需要依赖& ...
- JavaScript对象(二)
Part One:对象的三个特性 原型(prototype) 类(class) 可扩展性(extensible attribute) 1,b.isPrototypeOf(o) //判断b是不是o的 ...
- 致远·面向人工智能-逐浪CMS v8.1.2全面发布[全球首个基于dotNET core3.0的中文CMS]
原文:https://www.z01.com/down/3484.shtml 再远, 我都不会停息, 因为技术而生, 因为技术而强, 这是逐浪软件的命与根! 全新打造, 三百多项超级功能, 助你十分钟 ...
- C语言 变量初始化二进制、八进制、十六进制
int a1 = 10; //十进制 int a2 = 0b10; //二进制 int a3 = 010; //八进制 int a4 = 0x10; //十六进制 打印的结果:
- Flask 之分析线程和协程
目录 flask之分析线程和协程 01 思考:每个请求之间的关系 02 threading.local 03 通过字典自定义threading.local 04 通过setattr和getattr实现 ...
- C++ json解决方案
前段时间用到C++来封装com 因此从数据转换上我采用的Json来当两种语言的传递方式,现做下json的序列化与反序列化方案的总结: Rapidjson 文档地址:http://rapidjson.o ...
- Java中static和final的解析
static关键字和final关键字是Java中一个难点&重点.本文通过static的用途.常见问题.final的用途.final常见问题,以及static和final的对比来解释这两个关键字 ...