从零开始学Java之高效的异步非阻塞AIO模型

作者 :孙玉昌,昵称【一一哥 】,另外【壹壹哥】也是我哦

千锋教育高级教研员、CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者

前言

在前两篇文章中,壹哥 给大家介绍了BIO和NIO模型。我们现在知道,NIO相比BIO在性能上已经有了较大提升,但随着技术地不断革新,现在又出现了更为高效先进的AIO技术,所以今天壹哥会给大家继续普及AIO的内容。

------------------------------前戏已做完,精彩即开始----------------------------

全文大约【2400】 字,不说废话,只讲可以让你学到技术、明白原理的纯干货!本文带有丰富的案例及配图视频,让你更好地理解和运用文中的技术概念,并可以给你带来具有足够启迪的思考......

配套开源项目资料

Github: github.com/SunLtd/Lear...

Gitee: gitee.com/sunyiyi/Lea...

一. AIO-异步非阻塞式IO

1. AIO简介

AIO(Asynchronous IO),异步非阻塞的I/O, 这是在JDK 7中出现的新特性。AIO是在NIO基础上进行的扩展,属于NIO中的一项重要功能,它能够更加高效地处理大量的I/O操作,帮助我们实现高性能、高并发的网络编程。AIO的 主要优点在于可以在不阻塞线程的情况下进行异步操作,相对于BIO同步阻塞I/O 和 NIO同步非阻塞I/O,AIO更加的灵活和高效,响应更加迅速,提高了并发处理的能力。

2. 工作原理

壹哥把AIO的工作原理给大家简单描述总结如下:

AIO的异步操作模型是基于事件和回调机制来实现的 * *与NIO模式不同,AIO操作不需要在准备好之后才能进行读写,只需要在完成时通知应用程序进行下一步处理即可 * *一般是在应用程序调用异步I/O操作时,操作系统会为该操作分配一个线程,在该I/O操作完成后就通知应用程序,应用程序只需要处理已经完成的操作即可。

总的来说,AIO的工作原理与NIO的Selector相似,但AIO是基于事件和回调的异步操作模型,而Selector是基于轮询的同步操作模型。

3. 三种AIO异步通道

在AIO模型中,给我们提供了如下三种异步通道:

AsynchronousSocketChannel、AsynchronousServerSocketChannel和AsynchronousDatagramChannel。

  • AsynchronousSocketChannel:这是一个异步的客户端Socket通道。我们可以使用它来创建一个连接到远程服务器的Socket连接,也可以使用它来读取和写入数据。
  • AsynchronousServerSocketChannel:这是一个异步的服务端Socket通道,基于TCP协议和事件驱动,通过注册回调函数来实现异步操作,我们可以使用它来接受来自客户端的连接请求。
  • AsynchronousDatagramChannel:该通道用于非阻塞地发送和接收UDP数据包,基于UDP协议和事件驱动,通过注册回调函数来实现异步操作,不需要建立连接。

4. AIO工作流程

一般情况下,AIO的工作流程会遵循如下步骤:

  1. 应用程序向操作系统注册需要进行异步I/O操作的事件,如读操作或写操作;
  2. 操作系统将事件加入到I/O事件队列中,等待I/O完成;
  3. 操作系统通知应用程序,已经有一个事件准备好进行I/O操作了,应用程序可以继续执行其它操作;
  4. 应用程序调用操作系统提供的方法,来获取I/O操作的结果。

接下来壹哥就通过具体的案例,来给大家演示如何基于AIO模型进行数据的读写,请大家继续往下看吧。

二. 代码实现

1. 定义服务端

首先壹哥定义一个AIO服务器端,这里我们会监听一个9999端口,大家要注意,这个端口是任意的,但不能和别的进程端口重复。

java 复制代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * @author 一一哥Sun
 */
public class AioServer {
    //定义一个网络端口
	private static final int PORT = 9999;

	public static void main(String[] args) throws Exception {
		// 创建一个AsynchronousServerSocketChannel对象
		AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open()
				.bind(new InetSocketAddress(PORT));
		System.out.println("Server started at port " + PORT);

		// 接受客户端发来的异步连接请求
		serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
			//处理成功时的请求
			@Override
			public void completed(AsynchronousSocketChannel channel, Void attachment) {
				// 接收下一次的连接
				serverChannel.accept(null, this);

				// 处理请求:读取客户端发来的信息,并向客户端返回消息
				handle(channel);
			}

			@Override
			public void failed(Throwable throwable, Void attachment) {
				// 失败时进行异常处理
				throwable.printStackTrace();
			}
		});

		// 阻塞程序的执行,保持服务器一直运行
		Thread.currentThread().join();
	}

	//读取和返回信息
	private static void handle(AsynchronousSocketChannel channel) {
		//创建一个缓冲区
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		try {
			//从缓冲区中读取数据
			Future<Integer> future = channel.read(buffer);
			//获取读取到的数据
			int readBytes = future.get();
			if (readBytes > 0) {
				//切换到读模式
				buffer.flip();
				String message = StandardCharsets.UTF_8.decode(buffer).toString();
				System.out.println("接收到客户端发来的消息: " + message);
			}
		} catch (ExecutionException | InterruptedException e) {
			e.printStackTrace();
		} finally {
			try {
				channel.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

}

壹哥给大家分析一下上面案例的执行步骤:

  • 当服务端监听到客户端发来的连接请求时,AsynchronousServerSocketChannel对象可以通过accept()方法来接受客户端发来的异步请求;
  • 接着我们使用CompletionHandler来处理连接事件,在AsynchronousServerSocketChannel的回调方法completed中获取AsynchronousSocketChannel对象用于和客户端进行通信;
  • 然后利用AsynchronousServerSocketChannel.accept()方法来接收下一个连接;
  • 接着将当前连接交给handle()方法处理。在handle()方法中,我们使用Future来读取客户端发来的数据;
  • 最后需要关闭连接。

以上案例只是一个简单的示例,实际上AIO还有更多的用途,比如异步文件IO操作、异步套接字通信等。大家需要注意的是,AIO虽然比BIO和NIO更加高效,但我们在使用AIO也要注意一些问题,比如AIO的线程模型和内存占用等。

2. 定义客户端

接着壹哥再定义一个简单的客户端代码,使用AsynchronousSocketChannel连接到一个服务端并发送一个请求。当客户端与服务端的连接建立好之后,completed方法会被调用。

java 复制代码
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutionException;

/**
 * @author 一一哥Sun
 */
public class AioClient {
	// 定义一个Channel客户端对象
	private AsynchronousSocketChannel client;

	public AioClient(String host, int port) throws Exception {
		// 打开链接,获取Channel对象
		client = AsynchronousSocketChannel.open();
		// 设置网络地址和端口
		InetSocketAddress serverAddress = new InetSocketAddress(host, port);

		// 客户端异步连接到服务器
		client.connect(serverAddress, null, new CompletionHandler<Void, Void>() {
			// 连接成功时的回调方法
			@Override
			public void completed(Void result, Void attachment) {
				System.out.println("server连接成功...");
			}
			// 连接失败时的回调方法
			@Override
			public void failed(Throwable exc, Void attachment) {
				System.out.println("server连接失败...");
			}
		});
	}

	// 定义一个发送消息的方法
	public void send(String message) throws InterruptedException, ExecutionException {
		// 缓冲区
		ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
		// 发送消息
		client.write(buffer).get();
		buffer.clear();
	}

	public static void main(String[] args) throws Exception {
		AioClient client = new AioClient("localhost", 9999);
		// 向服务端发送消息
		client.send("Hello, Server,我是客户端!");
	}
}

在上面的案例中,我们在AioClient类的构造方法中,通过AsynchronousSocketChannel.open()方法创建了一个 AsynchronousSocketChannel对象,并调用connect()方法来发起连接请求。该连接请求是异步的,因此需要提供一个CompletionHandler对象,当连接成功或失败时,该对象的相关回调方法会被调用。

在send()方法中,我们利用ByteBuffer缓冲区将消息转换为字节序列,并调用 AsynchronousSocketChannel.write()方法发送消息。由于write()方法也是异步的,因此我们需要通过get()方法等待写操作的完成。

最后,我们在main()方法中创建一个 AioClient对象,并通过send()方法发送一条消息。

------------------------------正片已结束,来根事后烟----------------------------

三. 结语

至此,壹哥 就用三篇文章把BIO、NIO和AIO模型给大家讲解完毕了。当然,壹哥并没有详细地讲解NIO和AIO模型,主要是带大家详细地学习了BIO模型,后面我会开辟专门的NIO和AIO模型专栏,敬请大家持续关注。

我们在面试时,有很多面试官都会考察这样一道面试题, "请说说BIO、NIO和AIO的区别" ,所以最后壹哥就把这道题目给大家梳理一下,算是IO流部分的收尾吧。

我们现在知道,BIO、NIO和AIO都是Java中用于处理网络通信及信息处理的API,但它们的实现方式和特点有所不同,主要是体现在以下方面:

1. BIO同步阻塞I/O

BIO是一种最为传统的I/O模型,当客户端发起请求时,服务端会把该请求封装成一个Socket对象,然后在该Socket上进行 I/O操作。这种模式的特点是代码简单,易于理解和使用。但它每个请求都会对应一个线程,当并发请求数量较大,需要大量线程时就会导致服务器性能的严重下降。

2. NIO同步非阻塞I/O

NIO是 Java 1.4中引入的一种新的I/O模型,它主要提供了三个核心组件:Channel、Buffer、Selector。在NIO模型中,客户端发起请求时,不会创建一个线程处理该请求,而是将该请求放入到一个请求队列中,然后通过一个单独的线程轮询这些请求。对于已经准备好的请求,最后其分配给对应的线程进行处理。相对于BIO模型,NIO模型可以使用较少的线程支持较大的并发连接数。

3. AIO异步非阻塞I/O

AIO是Java 1.7中引入的一种 I/O模型,它和NIO一样,采用了非阻塞I/O的方式。但它和 NIO的区别在于,AIO 中的 I/O 操作不需要通过 Selector来轮询,而是由操作系统在数据准备好之后,主动通知应用程序进行处理。这种方式相对NIO更加的高效,因为AIO不需要将就绪的事件一个一个地进行轮询,而是由操作系统来进行通知,减少了不必要的上下文切换。

总的来说,BIO、NIO 和 AIO模型各有优劣,我们在开发时应该根据具体的应用场景进行选择。如果是并发量较小的应用程序,可以选择BIO模型;如果并发量较大,可以考虑使用NIO;如果是要处理大量的并发请求,应该选择使用AIO模型。

如果你独自学习觉得有很多困难,可以加入壹哥的学习互助群,大家一起交流学习。

相关推荐
用户685453759776937 分钟前
同步成本换并行度:多线程、协程、分片、MapReduce 怎么选才不踩坑
后端
javaTodo44 分钟前
Claude Code 记忆机制详解:从 CLAUDE.md 到 Auto Memory,六层体系全拆解
后端
LSTM971 小时前
使用 C# 和 Spire.PDF 从 HTML 模板生成 PDF 的实用指南
后端
JaguarJack1 小时前
为什么 PHP 闭包要加 static?
后端·php·服务端
BingoGo1 小时前
为什么 PHP 闭包要加 static?
后端
是糖糖啊2 小时前
OpenClaw 从零到一实战指南(飞书接入)
前端·人工智能·后端
百度Geek说2 小时前
基于Spark的配置化离线反作弊系统
后端
后端AI实验室2 小时前
用AI写代码,我差点把漏洞发上线:血泪总结的10个教训
java·ai
Java编程爱好者2 小时前
虚拟线程深度解析:轻量并发编程的未来趋势
后端