同步、异步、阻塞和非阻塞
在搞清楚三者之前,先来看看几个概念
-
同步(Synchronous):
- 同步指的是程序按顺序执行,并在调用一个操作时等待该操作完成后再继续执行下一步。
- 在同步操作中,当一个请求发出后,程序会阻塞等待操作的完成,直到结果返回。
- 在同步模式下,应用程序需要主动等待操作的完成,并在等待期间不能执行其他任务。
-
异步(Asynchronous):
- 异步指的是程序在请求操作后,不会立即等待操作完成,而是继续执行后续的操作。
- 在异步操作中,应用程序不需要主动等待操作的完成,而是通过回调、轮询等方式获取结果。
- 异步模式下,应用程序可以在操作进行的同时执行其他任务,提高了资源利用率和系统的响应性。
-
阻塞(Blocking):
- 阻塞是指在进行I/O操作时,当前线程会一直等待,直到操作完成或出现错误。
- 阻塞操作会导致线程被挂起,不能执行其他任务,直到阻塞的操作完成。
- 典型的阻塞操作包括传统的BIO模型,在读取或写入数据时,如果数据未准备好或无法立即处理完,线程会一直阻塞等待。
-
非阻塞(Non-blocking):
- 非阻塞是指在进行I/O操作时,当前线程不会被挂起,它会立即返回并继续执行后续的操作。
- 非阻塞操作不会等待操作的完成,如果操作不能立即完成,会返回一个特定的状态或错误码,告知操作当前无法执行。
- 典型的非阻塞操作包括NIO中的非阻塞模式,它可以通过轮询或选择器来检测通道是否准备好进行读取或写入操作,而无需阻塞等待。
java中IO模型
然后我们再来看这三种IO模型:
-
BIO(Blocking I/O):
- BIO是最传统的IO模型,也称为同步阻塞IO。
- 在BIO中,I/O操作是阻塞的,即当一个线程执行I/O操作时,它会被阻塞,直到数据完全读取或写入完成。
- 通常,每个连接都需要创建一个独立的线程来处理I/O操作,这限制了系统的可扩展性。
- BIO适用于连接数比较少且请求处理时间较短的场景,如传统的Socket编程
示例代码:
// 创建服务器Socket并监听端口
ServerSocket serverSocket = new ServerSocket(8080);
// 接受客户端连接请求
Socket clientSocket = serverSocket.accept();
// 获取客户端输入流
InputStream input = clientSocket.getInputStream();
// 读取客户端发送的数据
byte[] buffer = new byte[1024];
int bytesRead = input.read(buffer);
// 处理数据
String message = new String(buffer, 0, bytesRead);
System.out.println("Received message: " + message);
// 关闭资源
input.close();
clientSocket.close();
serverSocket.close();
-
NIO(Non-blocking I/O):
- NIO是Java在JDK 1.4中引入的新IO模型,也称为同步非阻塞IO。
- 在NIO中,通过使用通道(Channel)和缓冲区(Buffer)的概念来进行I/O操作。
- NIO提供了选择器(Selector)机制,一个线程可以通过选择器同时管理多个通道。
- NIO中的I/O操作是非阻塞的,可以异步地进行读取和写入,并且可以在数据到达之前进行其他操作,提高了并发处理能力。
- NIO适用于连接数较多、但每个连接的请求处理时间较短的场景,如Web服务器。
示例代码:
// 创建Selector
Selector selector = Selector.open();
// 创建服务器Socket通道并绑定端口
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.socket().bind(new InetSocketAddress(8080));
serverChannel.configureBlocking(false);
// 将通道注册到Selector上,并监听连接事件
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
// 阻塞等待就绪的事件
selector.select();
// 获取就绪事件的SelectionKey集合
Set<SelectionKey> selectedKeys = selector.selectedKeys();
// 遍历处理就绪事件
for (SelectionKey key : selectedKeys) {
if (key.isAcceptable()) {
// 接受客户端连接请求
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = server.accept();
clientChannel.configureBlocking(false);
// 将客户端通道注册到Selector上,并监听读事件
clientChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
// 读取客户端发送的数据
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = client.read(buffer);
buffer.flip();
if (bytesRead > 0) {
byte[] data = new byte[bytesRead];
buffer.get(data);
String message = new String(data);
System.out.println("Received message: " + message);
} else if (bytesRead < 0) {
// 客户端关闭连接,取消注册
key.cancel();
client.close();
}
}
}
}
-
AIO(Asynchronous I/O):
- AIO是Java在JDK 1.7中引入的新异步非阻塞IO模型。
- AIO使用了回调机制,当I/O操作完成时,将通知应用程序进行处理。
- AIO相较于NIO,更加适合处理大量的并发连接,每个连接的请求处理时间较长的场景。
- AIO通过使用操作系统提供的异步通道进行I/O操作,将I/O任务交给操作系统来完成,而不需要应用程序等待I/O操作完成。
示例代码:
// 创建AsynchronousServerSocketChannel
AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open();
// 绑定端口并开始监听
serverChannel.bind(new InetSocketAddress(8080));
// 开始接收客户端连接
serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
@Override
public void completed(AsynchronousSocketChannel clientChannel, Void attachment) {
// 处理客户端连接成功的操作
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 异步读取客户端发送的数据
clientChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer bytesRead, ByteBuffer buffer) {
buffer.flip();
if (bytesRead > 0) {
byte[] data = new byte[bytesRead];
buffer.get(data);
String message = new String(data);
System.out.println("Received message: " + message);
}
// 继续异步读取下一批数据
clientChannel.read(buffer, buffer, this);
}
@Override
public void failed(Throwable exc, ByteBuffer buffer) {
// 处理读取失败的操作
}
});
// 继续接收下一个客户端连接
serverChannel.accept(null, this);
}
@Override
public void failed(Throwable exc, Void attachment) {
// 处理接收连接失败的操作
}
});
// 阻塞主线程,保持服务器运行
Thread.sleep(Integer.MAX_VALUE);