1、同步异步、阻塞非阻塞概念
同步和异步是针对应用程序和内核的交互而言的。 阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作函数的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入函数会立即返回一个状态值。
由上描述基本可以总结一句简短的话,同步和异步是目的,阻塞和非阻塞是实现方式。
(1)同步指的是用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪自己上街买衣服,自己亲自干这件事,别的事干不了。
(2)异步异步是指用户进程触发IO操作以后便开始做自己的事情,而当IO操作已经完成的时候会得到IO完成的通知(异步的特点就是通知)告诉朋友自己合适衣服的尺寸,大小,颜色,让朋友委托去卖,然后自己可以去干别的事。(使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS)
(3)阻塞所谓阻塞方式的意思是指, 当试图对该文件描述符进行读写时, 如果当时没有东西可读,或者暂时不可写, 程序就进入等待 状态, 直到有东西可读或者可写为止去公交站充值,发现这个时候,充值员不在(可能上厕所去了),然后我们就在这里等待,一直等到充值员回来为止。(当然现实社会,可不是这样,但是在计算机里确实如此。)
(4)非阻塞非阻塞状态下, 如果没有东西可读, 或者不可写, 读写函数马上返回, 而不会等待,银行里取款办业务时,领取一张小票,领取完后我们自己可以玩玩手机,或者与别人聊聊天,当轮我们时,银行的喇叭会通知,这时候我们就可以去了。
2.什么是Java NIO?
同步非阻塞io模式,拿烧开水来说,NIO的做法是叫一个线程不断的轮询每个水壶的状态,看看是否有水壶的状态发生了改变,从而进行下一步的操作。
Java NIO有三大组成部分:Buffer,Channel,Selector,通过事件驱动模式实现了什么时候有数据可读的问题。
Channel:相当于IO操作的载体,相当于一个硬件设备,一个文件,一个socket或是区别程序中的不同IO操作,如read,write。
channel类似流,但又有些不同:
既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。
通道可以异步地读写。
通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。
Buffer:用于和NIO通道进行交互。如你所知,数据是从通道读入缓冲区,从缓冲区写入到通道中的。
缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。
channel 和 buffer 之间的交互如下:
CHANNEL BUFFER.png
Selector:Selector(选择器)是Java NIO中能够检测一到多个NIO通道,通道将关心的事件注册到selector 上,selector能够知晓通道是否为这些事件诸如读写事件做好数据准备。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。
selector工作模式.png
2.什么是Java BIO?
同步阻塞IO模式,数据的读取写入必须阻塞在一个线程内等待其完成。这里使用那个经典的烧开水例子,这里假设一个烧开水的场景,有一排水壶在烧开水,BIO的工作模式就是, 叫一个线程停留在一个水壶那,直到这个水壶烧开,才去处理下一个水壶。但是实际上线程在等待水壶烧开的时间段什么都没有做。不知道io操作中什么时候有数据可读,所以一直是阻塞的模式。
3.区别及应用
主要区别如下:
两种模式的差异对比:
首先,线程是较为重量级的资源。bio当并发量大,而后端服务或客户端处理数据慢时就会产生产生大量线程处于等待中,即上述的阻塞,是非常严重的资源浪费。此外,线程的切换也会导致cpu资源的浪费,单机内存限制也无法过多的线程,只能单向以流的形式读取数据。
nio使用单线程或者只使用少量的多线程,多个连接共用一个线程,消耗的线程资源会大幅减小。并且当处于等待(没有事件)的时候线程资源可以释放出来处理别的请求,通过事件驱动模型当有accept/read/write等事件发生后通知(唤醒)主线程分配资源来处理相关事件。以buffer缓冲区的形式处理数据,处理更为方便。
nio server demo:
Selector selector = Selector.open();
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
ssc.socket().bind(new InetSocketAddress(port));
ssc.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
// select()阻塞,等待有事件发生唤醒
int selected = selector.select();
if (selected > 0) {
Iterator selectedKeys = selector.selectedKeys().iterator();
while (selectedKeys.hasNext()) {
SelectionKey key = selectedKeys.next();
if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {
SocketChannel client = ((ServerSocketChannel) key.channel()).accept();
// 处理 accept 事件
//注册read事件
client.configureBlocking(false);
client.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufSize));
} else if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
// 处理 read 事件
//注册write事件
} else if ((key.readyOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) {
// 处理 write 事件
}
selectedKeys.remove();
}
}
}
bio server demo
ServerSocket serverSocket;
try {
serverSocket = new ServerSocket(8000);
while (true){
Socket socket = serverSocket.accept();
new Thread(()->{
try (InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream()) {
byte[] bytes =new byte[1024];
while (inputStream.read(bytes) != -1){
outputStream.write(bytes);
bytes = new byte[1024];
}
}catch (IOException e){
e.printStackTrace();
}
}).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}