1、同步、异步、阻塞、非阻塞
同步
| 指的是用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪 | 自己上街买衣服,自己亲自干这件事,别的事干不了。 |
异步 | 异步是指用户进程触发IO操作以后便开始做自己的事情,而当IO操作已经完成的时候会得到IO完成的通知(异步的特点就是通知) | 告诉朋友自己合适衣服的尺寸,大小,颜色,让朋友委托去卖,然后自己可以去干别的事。(使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS) |
阻塞 | 所谓阻塞方式的意思是指, 当试图对该文件描述符进行读写时, 如果当时没有东西可读,或者暂时不可写, 程序就进入等待 状态, 直到有东西可读或者可写为止 | 去衣服专卖店,发现这个时候,销售员不在(可能上厕所去了),然后我们就在这里等待,一直等到销售员回来为止。
|
非阻塞 | 非阻塞状态下, 如果没有东西可读, 或者不可写, 读写函数马上返回, 而不会等待, | 银行里取款办业务时,领取一张小票,领取完后我们自己可以玩玩手机,或者与别人聊聊天,当轮我们时,银行的喇叭会通知,这时候我们就可以去了。 |
同步阻塞IO(JAVA BIO):
同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
同步非阻塞IO(Java NIO) :
同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。用户进程也需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问。
异步阻塞IO(Java NIO):
此种方式下是指应用发起一个IO操作以后,不等待内核IO操作的完成,等内核完成IO操作以后会通知应用程序,这其实就是同步和异步最关键的区别,同步必须等待或者主动的去询问IO是否完成,那么为什么说是阻塞的呢?因为此时是通过select系统调用来完成的,而select函数本身的实现方式是阻塞的,而采用select函数有个好处就是它可以同时监听多个文件句柄(如果从UNP的角度看,select属于同步操作。因为select之后,进程还需要读写数据),从而提高系统的并发性!
(Java AIO(NIO.2))异步非阻塞IO:
在此种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了。
BIO、NIO、AIO适用场景分析:
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
2、JAVA NIO原理
答:NIO中有几个核心对象需要掌握:缓冲区(Buffer)、通道(Channel)、选择器(Selector)=》网络编程。
(1) 缓冲区Buffer:缓冲区对象本质上是一个数组。任何时候访问 NIO 中的数据,都是将它放到缓冲区中
Position:指定了下一个将要被写入或者读取的元素索引
Limit:指定还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)
Capacity:指定了可以存储在缓冲区中的最大数据容量
0 <= position <= limit <= capacity
缓冲区的分配:ByteBuffer buffer1 =ByteBuffer.allocate(10);
缓冲区分片:在现有缓冲区上切出一片来作为一个新的缓冲区,但现有的缓冲区与创建的子缓冲区在底层数组层面上是数据共享的。ByteBuffer slice = buffer.slice();
只读缓冲区:返回一个与原缓冲区完全相同的缓冲区,并与原缓冲区共享数据,只不过它是只读的。ByteBuffer readonly = buffer.asReadOnlyBuffer();
直接缓冲区:尝试避免将缓冲区的内容拷贝到一个中间缓冲区中 或者从一个中间缓冲区中拷贝数据。ByteBuffer buffer = ByteBuffer.allocateDirect( 1024 );
内存映射文件I/O:内存映射文件I/O是一种读和写文件数据的方法。一般来说,只有文件中实际读取或者写入的部分才会映射到内存中。MappedByteBuffer mbb = fc.map( FileChannel.MapMode.READ_WRITE, start, size );
(2) 通道Channel:通道是一个对象,通过它可以读取和写入数据。永远不会将字节直接写入通道中,相反是将数据写入包含一个或者多个字节的缓冲区。同样不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。
(3) Selector
/*
* 注册事件
* */
protected Selector getSelector() throws IOException {
// 创建Selector对象
Selector sel = Selector.open();
// 创建可选择通道,并配置为非阻塞模式
ServerSocketChannel server = ServerSocketChannel.open();
server.configureBlocking(false);
// 绑定通道到指定端口
ServerSocket socket = server.socket();
InetSocketAddress address = new InetSocketAddress(port);
socket.bind(address);
// 向Selector中注册感兴趣的事件
server.register(sel, SelectionKey.OP_ACCEPT);
return sel;
}

3、NIO Buffer缓存区
答:(1)
属性 描述
Capacity 容量,即可以容纳的最大数据量;在缓冲区创建时被设定并且不能改变
Limit 上界,缓冲区中当前数据量
Position 位置,下一个要被读或写的元素的索引
Mark 标记,调用mark()来设置mark=position,再调用reset()可以让position恢复到标记的位置即position=mark

(2)flip():将一个处于存数据状态的缓冲区变为一个处于准备取数据的状态。
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}

(3)rewind():rewind()方法与flip()很相似,区别在于rewind()不会影响limit,而flip()会重设limit属性值
(4)mark():标记就是记住当前位置(使用mark()方法标记),之后可以将位置恢复到标记处(使用reset()方法恢复)
public final Buffer mark() {
mark = position;
return this;
}
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}

补充:
(1)Flip()函数将一个能够继续添加数据元素的填充状态的缓冲区翻转成一个准备读出元素的释放状态。将postion=0、limit=实际大小
如果连续2次flip(),将缓冲区实际大小变成0,将position=0、limit=0
(2)Rewind()函数重读已经被翻转的缓冲区中的数据,将position=0
(3)ByteBuffer从写转到读状态:flip()
ByteBuffer重读:rewind()
ByteBuffer从读状态转到写状态(追加):
buffer.position(buffer.limit());
buffer.limit(buffer.capacity());
ByteBuffer从读状态转到写状态(重写入):
buffer.position(0);
buffer.limit(buffer.capacity());
buffer.put((byte)'K');
buffer.flip();
(4)0 <= mark <= position <= limit <= capacity