文档章节

Java NIO怎么理解通道和非阻塞?

kim_o
 kim_o
发布于 2017/07/20 23:31
字数 2202
阅读 7
收藏 0
点赞 0
评论 0

nio引入了buffer、channel、selector等概念。

通道相当于之前的I/O流。

“通道”太抽象了。Java解释不清的东西只能看它底层是怎么解释的——操作系统的I/O控制,通道控制方式?

I/O设备:CPU——通道——设备控制器——I/O设备

(通道和设备控制器的关系是多对多,设备控制器和I/O设备的关系也是多对多。)

I/O过程,参考http://www.nbrkb.net/lwt/jsjsj/asm/INTR&DMA.htm

1.CPU在执行用户程序时遇到I/O请求,根据用户的I/O请求生成通道程序(也可以是事先编好的)。放到内存中,并把该通道程序首地址放入CAW中。
2.CPU执行“启动I/O”指令,启动通道工作。

3.通道接收“启动I/O”指令信号,从CAW(记录下一条通道指令存放的地址)中取出通道程序首地址,并根据此地址取出通道程序的第一条指令,放入CCW(记录正在执行的通道指令)中;同时向CPU发回答信号,通知“启动I/O”指令完成完毕,CPU可继续执行。

4.与此同时,通道开始执行通道程序,进行物理I/O操作。当执行完一条指令后,如果还有下一条指令则继续执行;否则表示传输完成,同时自行停止,通知CPU转去处理通道结束事件,并从CCW中得到有关通道状态。

如此一来,主处理器只要发出一个I/O操作命令,剩下的工作完全由通道负责。I/O操作结束后,I/O通道会发出一个中断请求,表示相应操作已完成。

通道控制方式是对数据块进行处理的,并非字节。

通道控制方式就是异步I/O,参考http://blog.csdn.net/historyasamirror/article/details/5778378

I/O分两段:1.数据从I/O设备到内核缓冲区。2.数据从内核缓冲区到应用缓冲区

I/O类型:

1.异步I/O不会产生阻塞,程序不会等待I/O完成,继续执行代码,等I/O完成了再执行一个什么回调函数,代码执行效率高。很容易联想到ajax。这个一般用于I/O操作不影响之后的代码执行。

2.阻塞I/O,程序发起I/O操作后,进程阻塞,CPU转而执行其他进程,I/O的两个步骤完成后,向CPU发送中断信号,进程就绪,等待执行。

3.非阻塞I/O并非都不阻塞,其实是第一步不阻塞,第二部阻塞。程序发起I/O操作后,进程一直检查第一步是否完成,CPU一直在循环询问,完成后,进程阻塞直到完成第二步。明白了!这个是“站着茅坑不拉屎”,CPU利用率最低的。逻辑和操作系统的程序直接控制方式一样。

阻塞不阻塞描述的是发生I/O时当前线程的状态。

以上是操作系统的I/O,那么java的nio又是怎样的呢?

个人觉得是模仿了通道控制方式。

先看看nio的示例代码:

服务端TestReadServer.java

[java] view plain copy

  1. import java.io.IOException;  
  2. import java.net.InetSocketAddress;  
  3. import java.net.ServerSocket;  
  4. import java.nio.ByteBuffer;  
  5. import java.nio.channels.SelectionKey;  
  6. import java.nio.channels.Selector;  
  7. import java.nio.channels.ServerSocketChannel;  
  8. import java.nio.channels.SocketChannel;  
  9. import java.util.Iterator;  
  10. import java.util.Set;  
  11.   
  12. public class TestReadServer {  
  13.       
  14.       
  15.     private  int flag = 0;  
  16.       
  17.     private  int BLOCK = 1024*1024*10;  
  18.       
  19.     private  ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);  
  20.       
  21.     private  ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);  
  22.     private  Selector selector;  
  23.   
  24.     public TestReadServer(int port) throws IOException {  
  25.         // 打开服务器套接字通道  
  26.         ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();  
  27.         // 服务器配置为非阻塞  
  28.         serverSocketChannel.configureBlocking(false);  
  29.         // 检索与此通道关联的服务器套接字  
  30.         ServerSocket serverSocket = serverSocketChannel.socket();  
  31.         // 进行服务的绑定  
  32.         serverSocket.bind(new InetSocketAddress(port));  
  33.         // 通过open()方法找到Selector  
  34.         selector = Selector.open();  
  35.         // 注册到selector,等待连接  
  36.         serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);  
  37.         System.out.println("Server Start----"+port+":");  
  38.     }  
  39.   
  40.   
  41.     // 监听  
  42.     private void listen() throws IOException {  
  43.         while (true) {  
  44.             // 选择一组键,并且相应的通道已经打开  
  45.             selector.select();  
  46.             // 返回此选择器的已选择键集。  
  47.             Set selectionKeys = selector.selectedKeys();  
  48.             Iterator iterator = selectionKeys.iterator();  
  49.             while (iterator.hasNext()) {          
  50.                 SelectionKey selectionKey = iterator.next();  
  51.                 iterator.remove();  
  52.                 handleKey(selectionKey);  
  53.             }  
  54.         }  
  55.     }  
  56.   
  57.     // 处理请求  
  58.     private void handleKey(SelectionKey selectionKey) throws IOException {  
  59.         // 接受请求  
  60.         ServerSocketChannel server = null;  
  61.         SocketChannel client = null;  
  62.         String receiveText;  
  63.         String sendText;  
  64.         int count=0;  
  65.         // 测试此键的通道是否已准备好接受新的套接字连接。  
  66.         if (selectionKey.isAcceptable()) {  
  67.             // 返回为之创建此键的通道。  
  68.             server = (ServerSocketChannel) selectionKey.channel();  
  69.             // 接受到此通道套接字的连接。  
  70.             // 此方法返回的套接字通道(如果有)将处于阻塞模式。  
  71.             client = server.accept();  
  72.             // 配置为非阻塞  
  73.             client.configureBlocking(false);  
  74.             // 注册到selector,等待连接  
  75.             client.register(selector, SelectionKey.OP_READ);  
  76.         } else if (selectionKey.isReadable()) {  
  77.             // 返回为之创建此键的通道。  
  78.             client = (SocketChannel) selectionKey.channel();  
  79.             //将缓冲区清空以备下次读取  
  80.             receivebuffer.clear();  
  81.             //读取服务器发送来的数据到缓冲区中  
  82.             System.out.println(System.currentTimeMillis());  
  83.             count = client.read(receivebuffer);   
  84.             System.out.println(System.currentTimeMillis() + "~"+count);  
  85.         }   
  86.     }  
  87.   
  88.       
  89.     public static void main(String[] args) throws IOException {  
  90.         // TODO Auto-generated method stub  
  91.         int port = 1234;  
  92.         TestReadServer server = new TestReadServer(port);  
  93.         server.listen();  
  94.     }  
  95. }  

客户端TestReadClient.java

[java] view plain copy

  1. import java.io.BufferedInputStream;  
  2. import java.io.File;  
  3. import java.io.FileInputStream;  
  4. import java.io.IOException;  
  5. import java.net.InetSocketAddress;  
  6. import java.nio.ByteBuffer;  
  7. import java.nio.channels.SelectionKey;  
  8. import java.nio.channels.Selector;  
  9. import java.nio.channels.SocketChannel;  
  10. import java.util.Iterator;  
  11. import java.util.Set;  
  12.   
  13. public class TestReadClient {  
  14.   
  15.       
  16.     private static int flag = 0;  
  17.       
  18.     private static int BLOCK = 1024*1024*10;  
  19.       
  20.     private static ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);  
  21.       
  22.     private static ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);  
  23.       
  24.     private final static InetSocketAddress SERVER_ADDRESS = new InetSocketAddress(  
  25.             "localhost", 1234);  
  26.   
  27.     public static void main(String[] args) throws IOException {  
  28.         // TODO Auto-generated method stub  
  29.         // 打开socket通道  
  30.         SocketChannel socketChannel = SocketChannel.open();  
  31.         // 设置为非阻塞方式  
  32.         socketChannel.configureBlocking(false);  
  33.         // 打开选择器  
  34.         Selector selector = Selector.open();  
  35.         // 注册连接服务端socket动作  
  36.         socketChannel.register(selector, SelectionKey.OP_CONNECT);  
  37.         // 连接  
  38.         socketChannel.connect(SERVER_ADDRESS);  
  39.         // 分配缓冲区大小内存  
  40.           
  41.         Set selectionKeys;  
  42.         Iterator iterator;  
  43.         SelectionKey selectionKey;  
  44.         SocketChannel client;  
  45.         String receiveText;  
  46.         String sendText;  
  47.         int count=0;  
  48.   
  49.         while (true) {  
  50.             //选择一组键,其相应的通道已为 I/O 操作准备就绪。  
  51.             //此方法执行处于阻塞模式的选择操作。  
  52.             selector.select();  
  53.             //返回此选择器的已选择键集。  
  54.             selectionKeys = selector.selectedKeys();  
  55.             //System.out.println(selectionKeys.size());  
  56.             iterator = selectionKeys.iterator();  
  57.             while (iterator.hasNext()) {  
  58.                 selectionKey = iterator.next();  
  59.                 if (selectionKey.isConnectable()) {  
  60.                     System.out.println("client connect");  
  61.                     client = (SocketChannel) selectionKey.channel();  
  62.                     // 判断此通道上是否正在进行连接操作。  
  63.                     // 完成套接字通道的连接过程。  
  64.                     if (client.isConnectionPending()) {  
  65.                         client.finishConnect();  
  66.                         System.out.println("完成连接!");  
  67.                         sendbuffer.clear();  
  68.                         BufferedInputStream br = new BufferedInputStream(new FileInputStream(new File("D:\\BigData.zip")));  
  69.                         byte[] b = new byte[BLOCK];  
  70.                         br.read(b);       
  71.                         sendbuffer.put(b);  
  72.                         sendbuffer.flip();  
  73.                         System.out.println(System.currentTimeMillis());  
  74.                         client.write(sendbuffer);  
  75.                         System.out.println(System.currentTimeMillis());  
  76.                     }  
  77.                     client.register(selector, SelectionKey.OP_READ);  
  78.                 } else if (selectionKey.isReadable()) {  
  79.                     client = (SocketChannel) selectionKey.channel();  
  80.                     //将缓冲区清空以备下次读取  
  81.                     receivebuffer.clear();  
  82.                     //读取服务器发送来的数据到缓冲区中  
  83.                     count=client.read(receivebuffer);  
  84.                     if(count>0){  
  85.                         receiveText = new String( receivebuffer.array(),0,count);  
  86.                         System.out.println("客户端接受服务器端数据--:"+receiveText);  
  87.                         client.register(selector, SelectionKey.OP_WRITE);  
  88.                     }  
  89.                 }   
  90.             }  
  91.             selectionKeys.clear();  
  92.         }  
  93.     }  
  94. }  

例子是TestReadClient向TestReadServer发送一个本地文件。TestReadServer收到后每次打印读取到的字节数。

如何体现异步I/O?

看看TestReadClient中的:

[java] view plain copy

  1. if (selectionKey.isConnectable()) {  
  2.     System.out.println("client connect");  
  3.     client = (SocketChannel) selectionKey.channel();  
  4.     // 判断此通道上是否正在进行连接操作。  
  5.     // 完成套接字通道的连接过程。  
  6.     if (client.isConnectionPending()) {  
  7.         client.finishConnect();  

如果没有client.finishConnect();这句等待完成socket连接,可能会报异常:java.nio.channels.NotYetConnectedException

异步的才不会管你有没有连接成功,都会执行下面的代码。这里需要人为的干预。

如果要证明是java的nio单独使用非阻塞I/O,真没办法!!!阻塞非阻塞要查看进程。。。

不过还有种说法,叫异步非阻塞。上面那段,是用异步方式创建连接,进程当然没有被阻塞。使用了finishConnect()这是人为将程序中止,等待连接创建完成(是模仿阻塞将当前进程阻塞掉,还是模仿非阻塞不断轮询访问,不重要了反正是程序卡住没往下执行)。

所以,创建连接的过程用异步非阻塞I/O可以解释的通。那read/write的过程呢?

根据上面例子的打印结果,可以知道这个过程是同步的,没执行完是不会执行下面的代码的。至于底下是使用阻塞I/O还是非阻塞I/O,对于应用级程序来说不重要了。

阻塞还是非阻塞,对于正常的开发(创立连接,从连接中读写数据)并没有多少的提升,操作过程都类似。

那NIO凭什么成为高性能架构的基础,比起IO,性能优越在哪里,接着猜。。。

Java nio有意模仿操作系统的通道控制方式,那他的底层是不是就是直接使用操作系统的通道?

通道中的数据是以块为单位的,之前的流是以字节为单位的,同样的数据流操作外设的次数较多。代码中channel都是针对ByteBuffer对象进行read/write的,而ByteBuffer又是ByteBuffer.allocate(BLOCK);这样创建的,是一个连续的块空间。

那ByteBuffer是不是也是模拟操作系统的缓存?

缓存在io也有,如BufferedInputStream。CPU和外设的速度差很多,缓存为了提高CPU使用率,等外设将数据读入缓存后,CPU再统一操作,不用外设读一次,CPU操作一次,CPU的效率会被拉下来。。。

© 著作权归作者所有

共有 人打赏支持
kim_o
粉丝 1
博文 59
码字总数 32160
作品 0
深圳
程序员
Java NIO 系列教程 -- delete

(一) Java NIO 概述 Java NIO 由以下几个核心部分组成: Channels Buffers Selectors 虽然Java NIO 中除此之外还有很多类和组件,但在我看来,Channel,Buffer 和 Selector 构成了核心的A...

数据之美 ⋅ 2013/06/09 ⋅ 4

Java NIO之Selector(选择器)

历史回顾: Java NIO 概览 Java NIO 之 Buffer(缓冲区) Java NIO 之 Channel(通道) 其他高赞文章: 面试中关于Redis的问题看这篇就够了 一文轻松搞懂redis集群原理及搭建与使用 超详细的Jav...

山川_84b6 ⋅ 05/16 ⋅ 0

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

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

宸明 ⋅ 04/20 ⋅ 0

2018年Java编程学习面试最全知识点总结

Java是一种可以撰写跨平台应用软件的面向对象的程序设计语言。Java 技术具有卓越的通用性、高效性、平台移植性和安全性,广泛应用于PC、数据中心、游戏控制台、科学超级计算机、移动电话和互...

Java小辰 ⋅ 05/14 ⋅ 0

Java NIO 之 Channel(通道)

历史回顾: Java NIO 概览 Java NIO 之 Buffer(缓冲区) 其他高赞文章: 面试中关于Redis的问题看这篇就够了 一文轻松搞懂redis集群原理及搭建与使用 一 Channel(通道)介绍 通常来说NIO中的所...

山川_84b6 ⋅ 05/15 ⋅ 0

高性能网络通信框架Netty-Netty客户端底层与Java NIO对应关系

5.1 Netty客户端底层与Java NIO对应关系 在讲解Netty客户端程序时候我们提到指定NioSocketChannel用于创建客户端NIO套接字通道的实例,下面我们来看NioSocketChannel是如何创建一个Java NIO里...

阿里加多 ⋅ 06/04 ⋅ 0

JAVA IO 以及 NIO 理解和学习记录

由于Netty,了解了一些异步IO的知识,JAVA里面NIO就是原来的IO的一个补充,本文主要记录下在JAVA中IO的底层实现原理,以及对Zerocopy技术介绍。 IO,其实意味着:数据不停地搬入搬出缓冲区而...

lv_dreamer ⋅ 04/24 ⋅ 0

smart-ioc 首版发布:为 Android 打造的国产 NIO 通信框架

项目背景 在几年前作者便开始NIO的学习与研究,并在码云上提交了第一个作品smart-socket(NIO版)。本来期望将其打造成异步非阻塞的通信框架,如同netty一样,却最终效果并不理想。恰逢Java ...

三刀蜀黍 ⋅ 05/28 ⋅ 13

Netty高性能架构的理解之道

Netty的简单介绍 Netty 是一个 NIO client-server(客户端服务器)框架,使用 Netty 可以快速开发网络应用,例如服务器和客户 端协议。 Netty 提供了一种新的方式来使开发网络应用程序,这种新...

烂猪皮 ⋅ 05/04 ⋅ 0

Java NIO入门之浅析I/O模型

Java NIO入门之浅析I/O模型 海子 Java架构沉思录 1周前 点击上方“Java架构沉思录”,选择“置顶公众号”。 有内涵、有价值的文章第一时间送达! 作者:海子 原文:http://www.cnblogs.com/...

颓废的幻想者 ⋅ 05/24 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

LVM

LVM: 硬盘划分分区成物理卷->物理卷组成卷组->卷组划分逻辑分区。 1.磁盘分区: fdisk /dev/sdb 划分几个主分区 输入t更改每个分区类型为8e(LVM) 使用partprobe生成分区的文件:如/dev/sd...

ZHENG-JY ⋅ 29分钟前 ⋅ 0

彻底删除Microsoft Office的方法

参照此链接彻底删除Office https://support.office.com/zh-cn/article/%e4%bb%8e-pc-%e5%8d%b8%e8%bd%bd-office-9dd49b83-264a-477a-8fcc-2fdf5dbf61d8?ui=zh-CN&rs=zh-CN&ad=CN......

Kampfer ⋅ 44分钟前 ⋅ 0

大盘与个股之间关系

大盘走多:积极出手 顺势加码 大盘走空: 少量出手 退场观望 大盘做头:逆势减码 少量操作 大盘做底 : 小量建仓 小量试单

guozenhua ⋅ 46分钟前 ⋅ 0

Day16 LVM(逻辑卷管理)与磁盘故障小案例

lvm详解 简述 LVM的产生是因为传统的分区一旦分区好后就无法在线扩充空间,也存在一些工具能实现在线扩充空间但是还是会面临数据损坏的风险;传统的分区当分区空间不足时,一般的解决办法是再...

杉下 ⋅ 52分钟前 ⋅ 0

rsync实现多台linux服务器的文件同步

一、首先安装rsync,怎样安装都行,rpm,yum,还是你用源码安装都可以。因为我用的是阿里云的ESC,yum install rsync就ok了。 二、配置rsync服务 1.先建立个同步数据的帐号 123 groupadd r...

在下头真的很硬 ⋅ 今天 ⋅ 0

前端基础(三):函数

字数:1685 阅读时间:5分钟 函数定义 在最新的ES规范中,声明函数有4中方法: -函数声明 -函数表达式 -构造函数Function -生成器函数 1.函数声明 语法: function name([param[, param2 [....

老司机带你撸代码 ⋅ 今天 ⋅ 0

Java虚拟机的Heap监狱

在Java虚拟机中,我是一个位高权重的大管家,他们都很怕我,尤其是那些Java 对象,我把他们圈到一个叫做Heap的“监狱”里,严格管理,生杀大权尽在掌握。 中国人把Stack翻译成“栈”,把Hea...

java高级架构牛人 ⋅ 今天 ⋅ 0

Spring MVC基本概念

只写Controller

颖伙虫 ⋅ 今天 ⋅ 0

微软重金收购GitHub的背后逻辑原来是这样的

全球最大的开发者社区GitHub网站花落谁家的问题已经敲定,微软最终以75亿美元迎娶了这位在外界看来无比“神秘”的小家碧玉。尽管此事已过去一些时日,但整个开发者世界,包括全球各地的开源社...

linux-tao ⋅ 今天 ⋅ 0

磁盘管理—逻辑卷lvm

4.10-4.12 lvm 操作流程: 磁盘分区-->创建物理卷-->划分为卷组-->划分成逻辑卷-->格式化、挂载-->扩容。 磁盘分区 注: 创建分区时需要更改其文件类型为lvm(代码8e) 分区 3 已设置为 Linu...

弓正 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部