还记得曾经去一家公司面试,当时已经工作三年了,对于基本的开发知识,做项目流程,开发技能,根据线上问题结合百度解决问题能力还是有的,面试问我用过dubbo、mybatis,了解底层原理否,
使用什么协议进行数据传输,性能之类的问题,在开发过程中,缓存内部的计算绝大部分都是快于IO的计算,解决性能问题关键就是尽可能的减少IO的操作,比如我们多次从数据库中取数据进行数据整合,
和一次把所需数据取出进行数据整合,效率是不一样的,但有时候在做API的时候,该多次还是要多次,因为联表查询这些操作会降低性能,尤其是调用频繁的接口我们要尽可能的保证接口效率,单表查询有时
也未尝不是一种解决效率问题的好方式,开发中一定要结合业务场景具体问题具体分析。说多了,下面开始聊下IO
本文的学习整理是基于《JAVA高并发核心编程-卷1》 尼恩著 读书笔记
作者博客地址:https://www.cnblogs.com/crazymakercircle/p/
了解更多JAVA后台知识整理:JAVA后台系列目录
1. IO读写的基本原理
操作系统分为:内核空间和用户空间
内核态进程可以调用系统资源、用户态空间必须向内核空间发送指令,才可进行操作系统资源
IO的读写基本都会用到 write & read ,并不是直接从系统资源的读写而是内核缓冲区和应用程序缓冲区的数据交换
为何涉及缓冲区、因为在数据交换过程中,异常情况的发生会导致数据恢复工作,直接从系统中操作数据很消耗资源。
数据交换流程图,对于细节感兴趣的可以去看书
2. 四种主要的IO模型
2.1 同步阻塞IO
从发起IO系统调用开始,一直到系统调用返回,在这段时间内,发起IO请求的Java进程(或者线程)是阻塞的.
直到返回成功后,应用进程才能开始处理用户空间的缓存区数据。阻塞IO响应快慢的关键是内核空间是否复制完成
2.2 同步非阻塞IO
1. 在内核缓冲区中没有数据的情况下,系统调用会立即返回,返回一个调用失败的信息。
2.在内核缓冲区中有数据的情况下,在数据的复制过程中系统调用是阻塞的,直到完成数据从内核缓冲复制到用户缓冲。
复制完成后,系统调用返回成功,用户进程(或者线程)可以开始处理用户空间的缓存数据。
这种模型就好比接口查询订单,查不到信息一直查,完全又用户空间进程控制查询进度
2.3 IO多路复用
在IO多路复用模型中,引入了一种新的系统调用,查询IO的就绪状态
依赖系统select/epoll系统调用,通过监视就绪状态判断是否返回用户进程。
IO多路复用模型的IO涉及两种系统调用,一种是IO操作的系统调用,另一种是select/epoll就绪查询系统调用
因此必须依赖于系统的支持。Java语言的NIO(New IO)组件,在Linux系统上,是使用的是epoll系统调用实现的。
所以,Java语言的NIO(New IO)组件所使用的,就是IO多路复用模型。
值得注意的是:本质上,select/epoll系统调用是阻塞式的,属于同步IO
由于操作系统对异步IO模型的支持力度及JDK异步IO的效率问题,因此Netty使用的就是该模型,资源利用率、效率上的到很大提升
2.4 异步IO
用户线程通过系统调用,向内核注册某个IO操作。内核在整个IO操作(包括数据准备、数据复制)完成后,
通知用户程序,用户执行后续的业务操作。
3. 服务器作为server端,可以支撑多少连接数呢?
liunx系统环境下,建立一个socket连接,就会打开一个系统文件描述符,这个平时是有限制的默认1024,很简答,大家写一个客户端程序去连接服务器端,
看看多少个就连不上了,就会出错,如果想调大连接数,就调大文件描述符。具体操作细节看书或者看作者博客。
4. NIO
面向对象流,操作字节无法随意更改顺序,NIO属于面向缓冲区编程,可以随意读取Buffer中的数据,NIO之所以是非阻塞的是因为不会阻塞系统read操作。
NIO调用需要操作系统支持
4.1 Buffer
NIO的Buffer的内部是一个内存块(数组),提供了一组比较有效的方法,用来进行写入和读取的交替访问
有8种缓冲区类,分别是:
ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、 IntBuffer、LongBuffer、ShortBuffer、MappedByteBuffer
三个重要的成员属性:
capacity(容量) 内部容量的大小,满了就不能写入了,一旦初始化,就不能再改变
position(读写位置)表示当前的位置。position属性的值与缓冲区的读写模式有关
可以使用(即调用)flip翻转方法,将缓冲区变成读取模式
limit(读写的限制) 表示可以写入或者读取的最大上限,其属性值的具体含义,也与缓冲区的读写模式有关
常用方法:
allocate():创建缓冲区IntBuffer.allocate(20)
put(): 写入到缓冲区 intBuffer.put(1)
flip(): 翻转 、转换读写模式 intBuffer.flip()
get(): 读数据 int j = intBuffer.get()
rewind():倒带,再读一遍 intBuffer.rewind();
mark():方法将当前position的值保存起来,放在mark属性中,让mark属性记住这个临时位置
reset():将mark的值恢复到position中
clear():清空缓冲区
使用流程:
1)使用创建子类实例对象的allocate( )方法,创建一个Buffer类的实例对象。
2)调用put( )方法,将数据写入到缓冲区中。
3)写入完成后,在开始读取数据前,调用Buffer.flip( )方法,将缓冲区转换为读模式。
4)调用get( )方法,可以从缓冲区中读取数据。
5)读取完成后,调用Buffer.clear( )方法或Buffer.compact()方法,将缓冲区转换为写入模式,可以继续写入
4.2 Channel
在NIO中,一个网络连接使用一个通道表示,所有的NIO的IO操作都是通过连接通道完成的。一个通道类似于输入流和输出流的结合体,可以读写操作
四种重要通道:
1)FileChannel文件通道,用于文件的数据读写;
2)SocketChannel套接字通道,用于Socket套接字TCP连接的数据读写;
3)ServerSocketChannel服务器套接字通道(或服务器监听通道),允许我们监听TCP连接请求,为每个监听到的请求,创建一个SocketChannel套接字通道;
4)DatagramChannel数据报通道,用于UDP协议的数据读写。
需要了解f方法从书中或者Jdk文档中
4.3 Selector
Selector 选择器为一个IO事件的监听与查询器。通过选择器,一个线程可以查询多个通道的IO事件的就绪状态。
通道注册到选择器中,通过选择器所提供的事件查询(select)方法,一个选择器只需要一个线程进行监控,选择器管理通道,节约了系统资源
选择器的使命是完成IO的多路复用,其主要工作是通道的注册、监听、事件查询事件,和通道的关系是一对多的关系
Channel.register(Selector sel,int ops)
sel: 指定通道注册到的选择器实例
ops: 指定选择器要监控的IO事件类型
四种事件类型:可读:SelectionKey.OP_READ、可写:SelectionKey.OP_WRITE、
连接:SelectionKey.OP_CONNECT、接收:SelectionKey.OP_ACCEPT
监控通道的多种事件,用“按位或”运算符来实现 int key = SelectionKey.OP_READ | SelectionKey.OP_WRITE
不是所有通道都能使用选择器,一条通道若能被选择,必须继承SelectableChannel类
SelectionKey 选择键就是那些被选择器选中的IO事件
使用步骤:
1)获取选择器实例、2)将通道注册到选择器中、3)轮询感兴趣的IO就绪事件(选择键集合)
深学习细节可网络查询或者看书
《JAVA高并发核心编程-卷1》 尼恩著
作者博客地址:https://www.cnblogs.com/crazymakercircle/p/
了解更多JAVA后台知识整理:JAVA后台系列目录