Nio、Bio、Aio

[TOC]

概念

BIO (Blocking I/O):同步阻塞I/O模式。

NIO (New I/O):同步非阻塞模式。

AIO (Asynchronous I/O):异步非阻塞I/O模型。

同步

同步就是发起一个调用后,被调用者未处理完请求之前,调用不返回。

通俗的例子描述同步就像:

你打电话问书店老板有没有《葵花宝典》这本书的时候,如果是同步机制,书店老板会说,你稍等,”我查一下”,然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。

异步

异步就是发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但是被调用者并没有返回结果,此时我们可以处理其他的请求,被调用者通常依靠事件,回调等机制来通知调用者其返回结果。

通俗的例子描述异步就像:

而异步机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调

此处参照知乎上关于此问题的回答:www.zhihu.com/question/19…

再次总结一下同步与异步:

同步与异步最大的区别就是被调用方执行方式返回时机,同步指的是被调用方做完事情之后再返回,异步指的是被调用方先返回,然后再做事情,做完之后再想办法通知调用方

阻塞与非阻塞

阻塞

阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。

非阻塞

非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。

还是上面买书的例子:

你打电话问书店老板有没有《葵花宝典》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果。

同步、异步和阻塞、非阻塞的区别

阻塞和同步不是一回事,同步,异步与阻塞,非阻塞针对的对象是不一样的,阻塞,非阻塞是说的调用者,同步,异步说的是被调用者

代码

Bio

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class test implements Runnable {
public static void main(String[] args) {
test t=new test();
Thread t1=new Thread(t);
t1.start();
try {
Socket client=new Socket(InetAddress.getLocalHost(),8888);
BufferedReader bufferedReader = new BufferedReader
(new InputStreamReader
(client.getInputStream()));
System.out.println("值: "+bufferedReader.readLine());
} catch (IOException e) {
e.printStackTrace();
}

}
@Override
public void run() {
try {
ServerSocket serverSocket=new ServerSocket(8888);
while (true)
{
Socket socket=serverSocket.accept();
try (PrintWriter out = new PrintWriter(socket.getOutputStream());) {
out.println("Hello world!");
out.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
  • 服务器端启动 ServerSocket,端口 0 表示自动绑定一个空闲端口。

  • 调用 accept 方法,阻塞等待客户端连接。

  • 利用 Socket 模拟了一个简单的客户端,只进行连接、读取、打印。

  • 当连接建立后,启动一个单独线程负责回复客户端请求。

    img

Nio

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;

public class NioExample extends Thread {
public void run() {
try (Selector selector = Selector.open();
ServerSocketChannel serverSocket = ServerSocketChannel.open();)
{// 创建 Selector 和 Channel
serverSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(), 8888));
serverSocket.configureBlocking(false);
// 注册到 Selector,并说明关注点
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select();// 阻塞等待就绪的 Channel,这是关键点之一
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iter = selectedKeys.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
// 生产系统中一般会额外进行就绪状态检查
sayHelloWorld((ServerSocketChannel) key.channel());
iter.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void sayHelloWorld(ServerSocketChannel server) throws IOException {
try (SocketChannel client = server.accept();) {
client.write(Charset.defaultCharset().encode("Hello world!"));
}
}
// 省略了与前面类似的 main
public static void main(String[] args) throws IOException {
NioExample server = new NioExample();
server.start();
try (Socket client = new Socket(InetAddress.getLocalHost(), 8888)) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(client.getInputStream()));
bufferedReader.lines().forEach(s -> System.out.println(s));
}
}

}

首先,通过 Selector.open() 创建一个 Selector,作为类似调度员的角色。

然后,创建一个 ServerSocketChannel,并且向 Selector 注册,通过指定 SelectionKey.OP_ACCEPT,告诉调度员,它关注的是新的连接请求。注意:为什么我们要明确配置非阻塞模式呢?这是因为阻塞模式下,注册操作是不允许的,会抛出 IllegalBlockingModeException 异常。

Selector 阻塞在 select 操作,当有 Channel 发生接入请求,就会被唤醒。

在 sayHelloWorld 方法中,通过 SocketChannel 和 Buffer 进行数据操作,在本例中是发送了一段字符串。

可以看到,在前面两个样例中,IO 都是同步阻塞模式,所以需要多线程以实现多任务处理。而 NIO 则是利用了单线程轮询事件的机制,通过高效地定位就绪的 Channel,来决定做什么,仅仅 select 阶段是阻塞的,可以有效避免大量客户端连接时,频繁线程切换带来的问题,应用的扩展能力有了非常大的提高。下面这张图对这种实现思路进行了形象地说明

img

https://juejin.im/post/5c8aea1df265da2de33f6a09

https://juejin.im/post/5d19820c6fb9a07ea42094e2#heading-4