<acronym id="s8ci2"><small id="s8ci2"></small></acronym>
<rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
<acronym id="s8ci2"></acronym>
<acronym id="s8ci2"><center id="s8ci2"></center></acronym>
0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

Java AIO又稱為NIO 2.0,難道它也是基于NIO來實現的?

OSC開源社區 ? 來源:得物技術 ? 2023-03-23 09:26 ? 次閱讀

1

前言

關于Java BIO、NIO、AIO的區別和原理,這樣的文章非常的多的,但主要還是在BIO和NIO這兩者之間討論,而關于AIO這樣的文章就少之又少了,很多只是介紹了一下概念和代碼示例。 在了解AIO時,有注意到以下幾個現象:

1、 2011年Java 7發布,里面增加了AIO稱之為異步IO的編程模型,但已經過去了近12年,平時使用的開發框架中間件,還是以NIO為主,例如網絡框架Netty、Mina,Web容器Tomcat、Undertow。

2、 Java AIO又稱為NIO 2.0,難道它也是基于NIO來實現的?

3、 Netty舍去了AIO的支持。

4、 AIO看起來只是解決了有無,發布了個寂寞。

這幾個現象不免會令很多人心存疑惑,所以決定寫這篇文章時,不想簡單的把AIO的概念再復述一遍,而是要透過現象, 如何分析、思考和理解Java AIO的本質。

2

什么是異步

2.1 我們所了解的異步

AIO的A是Asynchronous異步的意思,在了解AIO的原理之前,我們先理清一下“異步”到底是怎樣的一個概念。 說起異步編程,在平時的開發還是比較常見,例如以下的代碼示例:


@Async
public void create() {
    //TODO
}


public void build() {
    executor.execute(() -> build());
}

不管是用@Async注解,還是往線程池里提交任務,他們最終都是同一個結果,就是把要執行的任務,交給另外一個線程來執行。

這個時候,可以大致的認為,所謂的“異步”,就是多線程,執行任務。

2.2 Java BIO和NIO到底是同步還是異步?

Java BIO和NIO到底是同步還是異步,我們先按照異步這個思路,做異步編程。 2.2.1BIO示例

byte [] data = new byte[1024];
InputStream in = socket.getInputStream();
in.read(data);
// 接收到數據,異步處理
executor.execute(() -> handle(data));


public void handle(byte [] data) {
    // TODO
}
BIO在read()時,雖然線程阻塞了,但在收到數據時,可以異步啟動一個線程去處理。 2.2.2NIO示例
selector.select();
Set keys = selector.selectedKeys();
Iterator 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()可以阻塞等待數據,在有數據可讀的時候,異步啟動一個線程,去讀取數據和處理數據。

2.2.3產生理解的偏差

此時我們信誓旦旦的說,Java的BIO和NIO是異步還是同步,取決你的心情,你高興給它個多線程,它就是異步的。 但果真如此么,在翻閱了大量博客文章之后,基本一致的闡明了,BIO和NIO是同步的。

那問題點出在哪呢,是什么造成了我們理解上的偏差呢? 那就是參考系的問題,以前學物理時,公交車上的乘客是運動還是靜止,需要有參考系前提,如果以地面為參考,他是運動的,以公交車為參考,他是靜止的。

Java IO也是一樣,需要有個參考系,才能定義它是同步異步,既然我們討論的是IO是哪一種模式,那就是要針對IO讀寫操作這件事來理解,而其他的啟動另外一個線程去處理數據,已經是脫離IO讀寫的范圍了,不應該把他們扯進來。

2.2.4嘗試定義異步

所以以IO讀寫操作這事件作為參照,我們先嘗試的這樣定義,就是發起IO讀寫的線程(調用read和write的線程),和實際操作IO讀寫的線程,如果是同一個線程,就稱之為同步,否則是異步。

顯然BIO只能是同步,調用in.read()當前線程阻塞,有數據返回的時候,接收到數據的還是原來的線程。

而NIO也稱之為同步,原因也是如此,調用channel.read()時,線程雖然不會阻塞,但讀到數據的還是當前線程。

按照這個思路,AIO應該是發起IO讀寫的線程,和實際收到數據的線程,可能不是同一個線程 是不是這樣呢,現在開始上Java AIO的代碼。

2.3 Java AIO的程序示例

2.3.1 AIO服務端程序


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() {


            @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 {
    @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) {


    }
}

2.3.2 AIO客戶端程序

public class AioClient {


    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);
 }
}

2.3.3 異步的定義猜想結論

分別運行服務端和客戶端程序

b0c449bc-c8e0-11ed-bfe3-dac502259ad0.png

在服務端運行結果里, main線程發起serverChannel.accept的調用,添加了一個CompletionHandler監聽回調,當有客戶端連接過來時,Thread-5線程執行了accep的completed回調方法。 緊接著Thread-5又發起了clientChannel.read調用,也添加了個CompletionHandler監聽回調,當收到數據時,是Thread-1的執行了read的completed回調方法。

這個結論和上面異步猜想一致,發起IO操作(例如accept、read、write)調用的線程,和最終完成這個操作的線程不是同一個,我們把這種IO模式稱之AIO, 當然了,這樣定義AIO只是為了方便我們理解,實際中對異步IO的定義可能更抽象一點。

3

AIO示例引發思考的問題

1、 執行completed()方法的這個線程是誰創建的,什么時候創建的?

2、 AIO注冊事件監聽和執行回調是如何實現的?

3、 監聽回調的本質是什么?

3.1 問題1:執行completed()方法的這個線程是誰創建的,什么時候創建的

一般,這樣的問題,需要從程序的入口的開始了解,但跟線程相關,其實是可以從線程棧的運行情況來定位線程是怎么運行。 只運行AIO服務端程序,客戶端不運行,打印一下線程棧(備注:程序在Linux平臺上運行,其他平臺略有差異)

b0dc3cfc-c8e0-11ed-bfe3-dac502259ad0.png

分析線程棧,發現,程序啟動了那么幾個線程

1、 線程Thread-0阻塞在EPoll.wait()方法上

2、 線程Thread-1、Thread-2。。。Thread-n(n和CPU核心數量一致)從阻塞隊列里take()任務,阻塞等待有任務返回。 此時可以暫定下一個結論: AIO服務端程序啟動之后,就開始創建了這些線程,且線程都處于阻塞等待狀態。

另外,發現這些線程的運行都跟Epoll有關系,提到Epoll,我們印象中,Java NIO在Linux平臺底層就是用Epoll來實現的,難道Java AIO也是用Epoll來實現么?為了證實這個結論,我們從下一個問題來展開討論

3.2 問題2:AIO注冊事件監聽和執行回調是如何實現的

帶著這個問題,去閱讀分析源碼時,發現源碼特別的長,而源碼解析是一項枯燥乏味的過程,很容易把閱讀者給逼走勸退掉。

對于長流程和邏輯復雜的代碼的理解,我們可以抓住它幾個脈絡,找出哪幾個核心流程。

以注冊監聽read為例clientChannel.read(...),它主要的核心流程是: 1、注冊事件 -> 2、監聽事件 -> 3、處理事件

3.2.1 1、注冊事件

b1688eaa-c8e0-11ed-bfe3-dac502259ad0.png

注冊事件調用EPoll.ctl(...)函數,這個函數在最后的參數用于指定是一次性的,還是永久性。

上面代碼events | EPOLLONSHOT字面意思看來,是一次性的。

3.2.2 2、監聽事件

b1a00cae-c8e0-11ed-bfe3-dac502259ad0.png

3.2.3 3、處理事件

b1cb0a94-c8e0-11ed-bfe3-dac502259ad0.pngb1f79adc-c8e0-11ed-bfe3-dac502259ad0.pngb2315420-c8e0-11ed-bfe3-dac502259ad0.png

3.2.4 核心流程總結

b250eede-c8e0-11ed-bfe3-dac502259ad0.png

在分析完上面的代碼流程后會發現,每一次IO讀寫都要經歷的這三個事件是一次性的,也就是在處理事件完,本次流程就結束了,如果想繼續下一次的IO讀寫,就得從頭開始再來一遍。這樣就會存在所謂的死亡回調(回調方法里再添加下一個回調方法),這對于編程的復雜度大大提高了。

3.3 問題3: 監聽回調的本質是什么?

先說一下結論,所謂監聽回調的本質,就是用戶態線程,調用內核態的函數(準確的說是API,例如read,write,epollWait),該函數還沒有返回時,用戶線程被阻塞了。

當函數返回時,會喚醒阻塞的線程,執行所謂回調函數。
對于這個結論的理解,要先引入幾個概念

3.3.1 系統調用與函數調用

函數調用: 找到某個函數,并執行函數里的相關命令 系統調用: 操作系統對用戶應用程序提供了編程接口,所謂API。

系統調用執行過程:

1.傳遞系統調用參數

2.執行陷入指令,用用戶態切換到核心態,這是因為系統調用一般都需要再核心態下執行

3.執行系統調用程序

4.返回用戶態

3.3.2 用戶態和內核態之間的通信

用戶態->內核態,通過系統調用方式即可。 內核態->用戶態,內核態根本不知道用戶態程序有什么函數,參數是啥,地址在哪里。所以內核是不可能去調用用戶態的函數,只能通過發送信號,比如kill 命令關閉程序就是通過發信號讓用戶程序優雅退出的。

既然內核態是不可能主動去調用用戶態的函數,為什么還會有回調呢,只能說這個所謂回調其實就是用戶態的自導自演。它既做了監聽,又做了執行回調函數。

3.3.3 用實際例子驗證結論

為了驗證這個結論是否有說服力,舉個例子,平時開發寫代碼用的IntelliJ IDEA,它是如何監聽鼠標、鍵盤事件和處理事件的。 按照慣例,先打印一下線程棧,會發現鼠標、鍵盤等事件的監聽是由"AWT-XAWT"線程負責的,處理事件則是"AWT-EventQueue"線程負責。

b28d98de-c8e0-11ed-bfe3-dac502259ad0.png

定位到具體的代碼上,可以看到"AWT-XAWT"正在做while循環,調用waitForEvents函數等待事件返回。如果沒有事件,線程就一直阻塞在那邊。

b2f3c492-c8e0-11ed-bfe3-dac502259ad0.png

4

Java AIO的本質是什么?

1、由于內核態無法直接調用用戶態函數,Java AIO的本質,就是只在用戶態實現異步。并沒有達到理想意義上的異步。

理想中的異步 何謂理想意義上的異步?這里舉個網購的例子 兩個角色,消費者A,快遞員B

A在網上購物時,填好家庭地址付款提交訂單,這個相當于注冊監聽事件

商家發貨,B把東西送到A家門口,這個相當于回調。

A在網上下完單,后續的發貨流程就不用他來操心了,可以繼續做其他事。B送貨也不關心A在不在家,反正就把貨扔到家門口就行了,兩個人互不依賴,互不相干擾。 假設A購物是用戶態來做,B送快遞是內核態來做,這種程序運行方式過于理想了,實際中實現不了。

現實中的異步 A住的是高檔小區,不能隨意進去,快遞只能送到小區門口。 A買了一件比較重的商品,比如一臺電視,因為A要上班不在家里,所以找了一個好友C幫忙把電視搬到他家。 A出門上班前,跟門口的保安D打聲招呼,說今天有一臺電視送過來,送到小區門口時,請電話聯系C,讓他過來拿。

此時,A下單并跟D打招呼,相當于注冊事件。在AIO中就是EPoll.ctl(...)注冊事件。

保安在門口蹲著相當于監聽事件,在AIO中就是Thread-0線程,做EPoll.wait(..)

快遞員把電視送到門口,相當于有IO事件到達。

保安通知C電視到了,C過來搬電視,相當于處理事件。在AIO中就是Thread-0往任務隊列提交任務,Thread-1 ~n去取數據,并執行回調方法。

整個過程中,保安D必須一直蹲著,寸步不能離開,否則電視送到門口,就被人偷了。 好友C也必須在A家待著,受人委托,東西到了,人卻不在現場,這有點失信于人。

所以實際的異步和理想中的異步,在互不依賴,互不干擾,這兩點相違背了。保安的作用最大,這是他人生的高光時刻。 異步過程中的注冊事件、監聽事件、處理事件,還有開啟多線程,這些過程的發起者全是用戶態一手操辦,所以說Java AIO只在用戶態實現了異步,這個和BIO、NIO先阻塞,阻塞喚醒后開啟異步線程處理的本質一致。

2、Java AIO跟NIO一樣,在各個平臺的底層實現方式也不同,在Linux是用EPoll,Windows是IOCP,Mac OS是KQueue。原理是大同小異,都是需要一個用戶線程阻塞等待IO事件,一個線程池從隊列里處理事件。

3、 Netty之所以移除掉AIO,很大的原因是在性能上AIO并沒有比NIO高。Linux雖然也有一套原生的AIO實現(類似Windows上的IOCP),但Java AIO在Linux并沒有采用,而是用EPoll來實現。

4、 Java AIO不支持UDP

5、 AIO編程方式略顯復雜,比如“死亡回調”






審核編輯:劉清

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • Linux
    +關注

    關注

    87

    文章

    11025

    瀏覽量

    207146
  • JAVA
    +關注

    關注

    19

    文章

    2911

    瀏覽量

    103280
  • AIO
    AIO
    +關注

    關注

    1

    文章

    61

    瀏覽量

    9926
  • Thread
    +關注

    關注

    2

    文章

    83

    瀏覽量

    25736

原文標題:透過現象看Java AIO的本質

文章出處:【微信號:OSC開源社區,微信公眾號:OSC開源社區】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    BIO、NIO、AIO 模型工作方式

    一、簡介 在計算機中,IO 傳輸數據有三種工作方式,分別是: BIO、NIO、AIO 。 在講解 BIO、NIO、AIO 之前,我們先來回顧一下這幾個概念: 同步與異步,阻塞與非阻塞
    的頭像 發表于 09-30 10:43 ?324次閱讀
    BIO、<b class='flag-5'>NIO</b>、<b class='flag-5'>AIO</b> 模型工作方式

    IO與NIO有何區別

    NIO 提到IO,這是Java提供的一套類庫,用于支持應用程序與內存、文件、網絡間進行數據交互,實現數據寫入與輸出。JDK自從1.4版本后,提供了另一套類庫NIO,我們平時習慣稱呼為N
    的頭像 發表于 09-25 11:00 ?453次閱讀
    IO與<b class='flag-5'>NIO</b>有何區別

    Java NIO編程理論基礎之Java IO及linux網絡IO模型發展

    Java NIO編程理論基礎篇——Java IO的發展以及linux網絡IO模型
    發表于 07-18 12:40

    Java NIO反應器模式設計

    Javal.4引入的NIO包里,最引人注目的是加入了非阻塞I/O。和IO包提供的阻塞模型不同,NIO在對一個非阻塞的連接進行操作時,調用會立即返回,而不是掛起等待.
    發表于 01-05 16:06 ?38次下載
    <b class='flag-5'>Java</b> <b class='flag-5'>NIO</b>反應器模式設計

    Java NIO (中文版)編程總結

    Java NIO 編程總結
    發表于 09-21 11:17 ?0次下載

    關于NIO的概述

    一.NIO中的幾個基礎概念 在NIO中有幾個比較關鍵的概念:Channel(通道),Buffer(緩沖區),Selector(選擇器)。 首先從Channel說起吧,通道,顧名思義,就是通向什么的
    發表于 09-26 17:07 ?0次下載
    關于<b class='flag-5'>NIO</b>的概述

    JavaNIO的基本概念

    一.NIO中的幾個基礎概念 在NIO中有幾個比較關鍵的概念:Channel(通道),Buffer(緩沖區),Selector(選擇器)。 首先從Channel說起吧,通道,顧名思義,就是通向什么的
    發表于 09-27 10:44 ?0次下載
    <b class='flag-5'>Java</b>之<b class='flag-5'>NIO</b>的基本概念

    蔚來推送L2級別自動輔助駕駛功能和NIO OS 2.0智能操作系統

    6月10日,NIO Pilot自動輔助駕駛系統迎來升級,本次新增7項功能;同時推送升級的還有NIO OS 2.0智能操作系統,全新的UX交互和全新的UI設計將帶來全新的數字化體驗。
    發表于 06-17 09:12 ?1146次閱讀

    蔚來NIO OS 2.5.0版本升級 NOMI體驗更加合理和完善

    2月20日,蔚來發布了NIO OS 2.5.0版本的升級,也是進入NIO OS 2.0階段第5次大升級,將會陸續通過FOTA對當前版本為2.3.0及以上的ES6和ES8進行推送,我們也
    發表于 03-08 08:59 ?2933次閱讀

    JAVANIO通過MappedByteBuffer操作大文件

    java io操作中通常采用BufferedReader,BufferedInputStream等帶緩沖的IO類處理大文件,不過java nio中引入了一種基于MappedByteBuffer操作大文件的方式,其讀寫性能極高,本
    的頭像 發表于 05-05 23:42 ?3297次閱讀

    Nio宣布客戶已經累計超過8億公里

    過優化算法和合理化道路監控以更好地為Nio車主提供服務,更多駕駛數據的積累可以幫助改善NIO Pilot的自動駕駛輔助系統。
    的頭像 發表于 07-20 09:50 ?2312次閱讀

    蔚來NIO Day發布的新產品都有哪些亮點?

    而此前經多輪預熱、成為焦點的蔚來新品發布環節,卻只占將近三個小時NIO Day的1/4時間,以至于被外界戲稱為“真的只是一個‘節目’”。
    的頭像 發表于 01-12 10:38 ?2580次閱讀

    蔚來NIO Phone發布:蔚來風格,旗艦選擇

    NIO Phone 亮點一覽 第二代驍龍8領先版 標志性天際線設計與純粹系統 高通3D Sonic Max超聲波指紋傳感器 5000萬像素三主攝 以車為中心的移動互聯全新體驗 今日,在2023
    的頭像 發表于 09-22 00:00 ?513次閱讀
    蔚來<b class='flag-5'>NIO</b> Phone發布:蔚來風格,旗艦選擇

    蔚來手機9月28日開始發貨 NIO Phone起售價6499

    蔚來手機9月28日開始發貨 NIO Phone起售價6499 蔚來造手機、小米造車,不知道以后華為會要造什么?或者蘋果造車? 蔚來用戶需要一款與蔚來汽車無縫連接的手機。所以蔚來手機來了
    的頭像 發表于 09-26 18:39 ?607次閱讀

    蔚來NIO Phone體驗報告:智能體驗超出預期,車手互聯頗有驚喜

    來了專為車主打造的NIO Link蔚來全景互聯技術,實現了車機與手機的生態系統閉環。本期體驗報告,不妨一起來看看NIO Phone帶來的多項創新驚喜吧。 蔚來出品,智能精品 作為蔚來首款智能手機,
    的頭像 發表于 12-15 20:45 ?763次閱讀
    蔚來<b class='flag-5'>NIO</b> Phone體驗報告:智能體驗超出預期,車手互聯頗有驚喜
    亚洲欧美日韩精品久久_久久精品AⅤ无码中文_日本中文字幕有码在线播放_亚洲视频高清不卡在线观看
    <acronym id="s8ci2"><small id="s8ci2"></small></acronym>
    <rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
    <acronym id="s8ci2"></acronym>
    <acronym id="s8ci2"><center id="s8ci2"></center></acronym>