1、引言關(guān)于Java網(wǎng)絡(luò)編程中的同步IO和異步IO的區(qū)別及原理的文章非常的多,具體來說主要還是在討論Java BIO和Java NIO這兩者,而關(guān)于Java AIO的文章就少之又少了(即使用也只是介紹了一下概念和代碼示例)。 在深入了解AIO之前,我注意到以下幾個現(xiàn)象:
Java AIO的這些不合常理的現(xiàn)象難免會令人心存疑惑。所以決定寫這篇文章時,我不想只是簡單的把AIO的概念再復述一遍,而是要透過現(xiàn)象,深入分析、思考和并理解Java AIO的本質(zhì)。 2、我們所理解的異步
public void create() {
???? //TODO
} ?
public void build() {
???? executor.execute(() -> build());
} ?
不管是用@Async注解,還是往線程池里提交任務(wù),他們最終都是同一個結(jié)果,就是把要執(zhí)行的任務(wù),交給另外一個線程來執(zhí)行。
這個時候,我們可以大致的認為,所謂的“異步”,就是用多線程的方式去并行執(zhí)行任務(wù)。 3、Java BIO和NIO到底是同步還是異步?Java BIO和NIO到底是同步還是異步,我們先按照異步這個思路,做異步編程。 3.1BIO代碼示例
InputStream in = socket.getInputStream(); in.read(data); // 接收到數(shù)據(jù),異步處理 executor.execute(() -> handle(data)); ?
public void handle( byte [] data) {
???? // TODO
} ?
如上:BIO在read()時,雖然線程阻塞了,但在收到數(shù)據(jù)時,可以異步啟動一個線程去處理。
3.2NIO代碼示例
Set<SelectionKey> keys = selector.selectedKeys(); Iterator<SelectionKey> iterator = keys.iterator(); while (iterator.hasNext()) {
???? SelectionKey key = iterator.next();
???? if (key.isReadable()) {
???????? SocketChannel channel = (SocketChannel) key.channel();
???????? ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
???????? executor.execute(() -> {
???????????? try {
???????????????? channel.read(byteBuffer);
???????????????? handle(byteBuffer);
???????????? } catch (Exception e) {
?
???????????? }
???????? });
???? }
} ?
public static void handle(ByteBuffer buffer) {
???? // TODO
} ?
同理:NIO雖然read()是非阻塞的,通過select()可以阻塞等待數(shù)據(jù),在有數(shù)據(jù)可讀的時候,異步啟動一個線程,去讀取數(shù)據(jù)和處理數(shù)據(jù)。
3.3產(chǎn)生的理解偏差此時我們信誓旦旦地說,Java的BIO和NIO是異步還是同步,取決你的心情,你高興給它個多線程,它就是異步的。 但果真如此么? 在翻閱了大量博客文章之后,基本一致的闡明了——BIO和NIO是同步的。 那問題點出在哪呢,是什么造成了我們理解上的偏差呢? 那就是參考系的問題,以前學物理時,公交車上的乘客是運動還是靜止,需要有參考系前提,如果以地面為參考,他是運動的,以公交車為參考,他是靜止的。 Java IO也是一樣,需要有個參考系,才能定義它是同步還是異步。 既然我們討論的是關(guān)于Java IO是哪一種模式,那就是要針對IO讀寫操作這件事來理解,而其他的啟動另外一個線程去處理數(shù)據(jù),已經(jīng)是脫離IO讀寫的范圍了,不應(yīng)該把他們扯進來。 3.4嘗試定義異步所以以IO讀寫操作這事件作為參照,我們先嘗試的這樣定義,就是:發(fā)起IO讀寫的線程(調(diào)用read和write的線程),和實際操作IO讀寫的線程,如果是同一個線程,就稱之為同步,否則是異步。 按上述定義:
按照這個思路,AIO應(yīng)該是發(fā)起IO讀寫的線程,和實際收到數(shù)據(jù)的線程,可能不是同一個線程。 是不是這樣呢?我們將在上一節(jié)直接上Java AIO的代碼,我們從 實際代碼中一窺究竟吧。 4、一個Java AIO的網(wǎng)絡(luò)編程示例4.1AIO服務(wù)端程序代碼?
public class AioServer {
?
???? public static void main(String[] args) throws IOException {
???????? System.out.println(Thread.currentThread().getName() + " AioServer start" );
???????? AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open()
???????????????? .bind( new InetSocketAddress( "127.0.0.1" , 8080 ));
???????? serverChannel.accept( null , new CompletionHandler<AsynchronousSocketChannel, Void>() {
?
???????????? @Override
???????????? public void completed(AsynchronousSocketChannel clientChannel, Void attachment) {
???????????????? System.out.println(Thread.currentThread().getName() + " client is connected" );
???????????????? ByteBuffer buffer = ByteBuffer.allocate( 1024 );
???????????????? clientChannel.read(buffer, buffer, new ClientHandler());
???????????? }
?
???????????? @Override
???????????? public void failed(Throwable exc, Void attachment) {
???????????????? System.out.println( "accept fail" );
???????????? }
???????? });
???????? System.in.read();
???? }
} ?
public class ClientHandler implements CompletionHandler<Integer, ByteBuffer> {
???? @Override
???? public void completed(Integer result, ByteBuffer buffer) {
???????? buffer.flip();
???????? byte [] data = new byte [buffer.remaining()];
???????? buffer.get(data);
???????? System.out.println(Thread.currentThread().getName() + " received:" ? + new String(data, StandardCharsets.UTF_8));
???? }
?
???? @Override
???? public void failed(Throwable exc, ByteBuffer buffer) {
?
???? }
} ?
4.2AIO客戶端程序
???? public static void main(String[] args) throws Exception {
???????? AsynchronousSocketChannel channel = AsynchronousSocketChannel.open();
???????? channel.connect( new InetSocketAddress( "127.0.0.1" , 8080 ));
???????? ByteBuffer buffer = ByteBuffer.allocate( 1024 );
???????? buffer.put( "Java AIO" .getBytes(StandardCharsets.UTF_8));
???????? buffer.flip();
???????? Thread.sleep(1000L);
???????? channel.write(buffer);
? }
} ?
4.3異步的定義猜想結(jié)論?文章來源地址http://www.zghlxwxcb.cn/news/detail-693438.html ? 在服務(wù)端運行結(jié)果里:
1)main線程發(fā)起serverChannel.accept的調(diào)用,添加了一個CompletionHandler監(jiān)聽回調(diào),當有客戶端連接過來時,Thread-5線程執(zhí)行了accep的completed回調(diào)方法。 2)緊接著Thread-5又發(fā)起了clientChannel.read調(diào)用,也添加了個CompletionHandler監(jiān)聽回調(diào),當收到數(shù)據(jù)時,是Thread-1的執(zhí)行了read的completed回調(diào)方法。 這個結(jié)論和上面異步猜想一致:發(fā)起IO操作(例如accept、read、write)調(diào)用的線程,和最終完成這個操作的線程不是同一個,我們把這種IO模式稱之AIO。 當然了,這樣定義AIO只是為了方便我們理解,實際中對異步IO的定義可能更抽象一點。 ?
5、 AIO示例引發(fā)思考1:“執(zhí)行completed()方法的線程是誰創(chuàng)建、什么時候創(chuàng)建?”一般,這樣的問題,需要從程序的入口的開始了解,但跟線程相關(guān),其實是可以從線程棧的運行情況來定位線程是怎么運行。 只運行AIO服務(wù)端程序,客戶端不運行,打印一下線程棧(備注:程序在Linux平臺上運行,其他平臺略有差異)。如下圖所示。 ?分析線程棧,發(fā)現(xiàn),程序啟動了那么幾個線程:
? 6、 AIO示例引發(fā)思考2:AIO注冊事件監(jiān)聽和執(zhí)行回調(diào)是如何實現(xiàn)的?
?注:注冊事件調(diào)用EPoll.ctl(...)函數(shù),這個函數(shù)在最后的參數(shù)用于指定是一次性的,還是永久性。上面代碼events | EPOLLONSHOT字面意思看來,是一次性的。 監(jiān)聽事件: ? 處理事件: ? ? ? 核心流程總結(jié): ?在分析完上面的代碼流程后會發(fā)現(xiàn):每一次IO讀寫都要經(jīng)歷的這三個事件是一次性的,也就是在處理事件完,本次流程就結(jié)束了,如果想繼續(xù)下一次的IO讀寫,就得從頭開始再來一遍。這樣就會存在所謂的死亡回調(diào)(回調(diào)方法里再添加下一個回調(diào)方法),這對于編程的復雜度大大提高了。 7、 AIO示例引發(fā)思考3:監(jiān)聽回調(diào)的本質(zhì)是什么?? 7.1概述
7.2系統(tǒng)調(diào)用與函數(shù)調(diào)用
? 7.3用戶態(tài)和內(nèi)核態(tài)之間的通信
7.4用實際例子驗證結(jié)論
?定位到具體的代碼上:可以看到“AWT-XAWT”正在做while循環(huán),調(diào)用waitForEvents函數(shù)等待事件返回。如果沒有事件,線程就一直阻塞在那邊。如下圖所示。 ? 8、Java AIO的本質(zhì)是什么?? 8.1Java AIO的本質(zhì),就是只在用戶態(tài)實現(xiàn)了異步
8.2Java AIO的其它真相
? |
到了這里,關(guān)于到底什么是Java AIO?為什么Netty會移除AIO?一文搞懂AIO的本質(zhì)!的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!