javaNIO学习笔记

什么是NIO

同步/异步 阻塞/非阻塞
Unix I/O模型:
输入操作分为两个阶段:等待数据准备好,从内核向进程复制数据
同步:阻塞式(调用未完成,进程挂起),非阻塞式(调用完成,返回失败结果),I/O多路复用(阻塞select,不阻塞I/O调用,进程阻塞于select调用,等待任一套接口变为可用,系统调用进行数据读取),信号驱动式I/O(sigio,内核通过信号告知数据准备好)
异步I/O(内核告诉你I/O完成)
从不同层面看:
CPU层面:操作系统对于cpu调度和io操作一般是异步非阻塞的方式。CPU发出io请求后不再等待io操作(数据放入磁盘控制器缓冲区),而是继续执行其他任务(非阻塞),DMA负责将数据读取到内核缓冲区,然后发送通知给CPU,这个过程io操作和cpu异步进行。
线程层面:操作系统为了应用层面的开发简单(程序运行结果和编程顺序相同),将底层的异步非阻塞封装为了同步阻塞(read,write方法等系统调用时,os会阻塞进程,让出cpu资源)。如果直接使用阻塞方法,会使线程挂起,如果使用非阻塞方式(轮询),都会使cpu资源浪费。解决办法:
1.多线程(同步阻塞)
2.IO多路复用(select,poll,epoll)(同步非阻塞,系统调用不再阻塞,线程阻塞于select函数)
3.异步IO接口,kernel-aio,IOCP
感知层次:对IO多路复用进行进一步封装(异步nio,netty等)

事件驱动reactor

一.成员

1.handle:由操作系统管理的资源。包括网络连接,打开的文件,计时器,同步对象等。
2.Synchronous Event Demultiplexer(同步事件分离器): 本身是一个系统调用,用于等待一组时间的发生。调用者会在这里阻塞,直到可以非阻塞的完成对handle的操作。也就是I/O多路复用,对于linux是select,epoll,对于java NIO就是selector。
3.Initiation Dispatcher(初始分发器):Reactor角色。定义了用于注册,删除和分派event handlers的接口。同步事件分离器负责等待新事件的发生,当它检测到新事件时,会通知初始分发器调用特定的应用程序的事件处理器。常见事件包括接受连接,数据io和超时事件。
4.Event Handler(事件处理器):由钩子方法组成的接口,该方法抽象的表示特定应用的某个服务的事件的调度操作。(java nio中没有实际的对应组件)(Netty中就有很多对应组件,ChannelInboundHandlerAdapter)
5.Concrete Event Handler(具体事件处理器):实现钩子方法,实现了特定业务的逻辑,本质上是我们编写的一个个的处理器。

二.运行过程

1.应用程序向分发器注册具体的事件处理器。处理器指定事件的类型,希望分发器通知它有关事件什么时候发生在连接的handle。
2.分发器会要求每个事件处理器传递它们内部的handle,这个handle向操作系统标识了事件处理器。
3.注册所有事件处理器后,应用程序调用handle_events方法启动分发器的事件循环。分发器将每个注册的事件处理器的handle组合起来,并使用同步事件分离器,等待事件在这些handle上发生。
4.但对应事件的handle变为“ready”时,同步事件分离器通知分发器。
5.分发器 会触发事件处理器钩子方法,从而响应这个处于ready状态的Handle。当事件发生时,分发器使用handles作为 key 来定位和调度适当的事件处理器的钩子方法。
6.分发器会回调事件处理器的handle_events钩子方法来执行特定于应用的功能(开发者自己所编写的功能),从而响应这个事件。发生的事件类型可以作为参数传递给这个方法,在方法内部使用,用来执行额外的特定于服务的多路分解和分派。

NIO 模块

channel

通道:主要用于文件的读写,不同于stream是面向多字节的块,而且是双向的。主要包括fileChannel,datagramChanne,socketChannle,serverSocketChannel。一个channel有多个事件,包括链接,确认,读,写。一个非阻塞的channel(fileChannel是阻塞的)会向selector注册自己,包含所感兴趣的事件集等信息。返回一个包含有注册相关信息的 selectionKey对象(感兴趣的事件集,准备好的事件集,channel和selector对象)。
scatter:从channel中读取数据分散写入到多个buffer
gather:多个buffer中的数据聚集到channel
transformFrom/transformTo:将数据从源通道传输到另一个通道
force:强制将此通道数据写入到存储设备

buffer

缓冲区:底层通过数组存储数据,包含对数据的结构化访问,数据的写入和读取都需要经过它。常用的缓冲区类型为byteBuffer。
属性:buff数组,position当前读取位置,mark标记的读过的位置标记(用于回退),capacity初始化容量,limit写数据时和容量一样大;读数据时代表buff中有效数据长度。
0<= mark <= position <= limit <= capacity
方法:
clear():数据写入前调用,position=0,limit=capacity
flip():数据读取前调用,limit=position,position=0

selector

多路复用器:底层调用seletc()不断轮询就绪的channel,返回准备好目标事件的channel数(这个数只包含上次调用到这次调用之间有多少新就绪的channel数,不是累计)。通过获取得到的selectedKeys列表,区分处理不同的事件。

线程安全

FileChannel线程安全,多个线程再同一个实例上并发调用而不会引起任何问题,影响通道位置和文件胆小的操作都是单线程的。
fileLock:
lock()阻塞式,独占锁
tryLock() 非阻塞式,独占锁
tryLock(0, Long.MAX_VALUE, true) 共享锁(多个线程同时持有,避免其他线程获取独占锁)

其他

mappedBytedBuffer:使用内存映射,将磁盘中的数据映射为虚拟内存,通过缺页中断获取数据。底层使用mmap()函数实现,可以复制或者修改文件。效率比单纯的read和write高,比零拷贝低。
directByteBuffer:使用堆外内存,减少文件到堆内存的一次复制(NIO使用内存地址管理byteBuffer,只使用堆内存会导致在GC过后,碎片整理时内存地址发生改变,所以需要复制数据到堆外内存再进行写入和读取)。使用allocateDirct分配,不被堆内存大小所限制,直接受操作系统管理。创建和销毁比heapbytebuffer更加消耗性能,适用于较少创建和销毁的大对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值