从零开始学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模型。

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

相关推荐
科技资讯早知道2 分钟前
java计算机毕设课设—坦克大战游戏
java·开发语言·游戏·毕业设计·课程设计·毕设
小比卡丘1 小时前
C语言进阶版第17课—自定义类型:联合和枚举
android·java·c语言
xmh-sxh-13141 小时前
java 数据存储方式
java
liu_chunhai1 小时前
设计模式(3)builder
java·开发语言·设计模式
姜学迁1 小时前
Rust-枚举
开发语言·后端·rust
爱学习的小健2 小时前
MQTT--Java整合EMQX
后端
北极小狐2 小时前
Java vs JavaScript:类型系统的艺术 - 从 Object 到 any,从静态到动态
后端
ya888g2 小时前
GESP C++四级样题卷
java·c++·算法
【D'accumulation】2 小时前
令牌主动失效机制范例(利用redis)注释分析
java·spring boot·redis·后端
小叶学C++2 小时前
【C++】类与对象(下)
java·开发语言·c++