BIO,NIO,AIO 總結自己的理解!

java中的b,a,n.io可以理解成java語音對操作系統的各種io模型的封裝。程序員在使用這些 api的時候,不需要關心關注操作系統層的東西。

先理解下幾個概念:同步與異步,阻塞與非阻塞。

同步與異步:

同步:同步就是發起一個調用後,被調用者沒有處理完,調用不返回。

異步:異步就是發起一個調用後,立即得到被調用的迴應,但是調用者可以去一邊玩會再來接收被調用者的回調返回的結果。

最大的區別在於:異步不需要等待,同步需要等待。

 

阻塞非阻塞:

阻塞:阻塞就是發起一個請求,調用者一直等待返回結果,也就是線程被掛起了,只有條件滿足了才能繼續。

非阻塞:非阻塞就是調用者不用等待結果返回,可以先去搞別的事情。

下面這個例子將的很好!

舉個生活中簡單的例子,你媽媽讓你燒水,小時候你比較笨啊,在哪裏傻等着水開(**同步阻塞**)。等你稍微再長大一點,你知道每次燒水的空隙可以去幹點其他事,然後只需要時不時來看看水開了沒有(**同步非阻塞**)。後來,你們家用上了水開了會發出聲音的壺,這樣你就只需要聽到響聲後就知道水開了,在這期間你可以隨便幹自己的事情,你需要去倒水了(**異步非阻塞**)。

1.BIO (Blocking I/O)

同步阻塞I/O模式,數據的讀取寫入必須阻塞在一個線程內等待其完成。

1.1 傳統 BIO

BIO通信(一請求一應答)模型圖如下(圖源網絡,原出處不明):

採用 **BIO 通信模型** 的服務端,通常由一個獨立的 Acceptor 線程負責監聽客戶端的連接。我們一般通過在`while(true)` 循環中服務端會調用 `accept()` 方法等待接收客戶端的連接的方式監聽請求,請求一旦接收到一個連接請求,就可以建立通信套接字在這個通信套接字上進行讀寫操作,此時不能再接收其他客戶端連接請求,只能等待同當前連接的客戶端的操作執行完成, 不過可以通過多線程來支持多個客戶端的連接,如上圖所示。

如果要讓 **BIO 通信模型** 能夠同時處理多個客戶端請求,就必須使用多線程(主要原因是`socket.accept()`、`socket.read()`、`socket.write()` 涉及的三個主要函數都是同步阻塞的),也就是說它在接收到客戶端連接請求之後爲每個客戶端創建一個新的線程進行鏈路處理,處理完成之後,通過輸出流返回應答給客戶端,線程銷燬。這就是典型的 **一請求一應答通信模型** 。我們可以設想一下如果這個連接不做任何事情的話就會造成不必要的線程開銷,不過可以通過 **線程池機制** 改善,線程池還可以讓線程的創建和回收成本相對較低。使用`FixedThreadPool` 可以有效的控制了線程的最大數量,保證了系統有限的資源的控制,實現了N(客戶端請求數量):M(處理客戶端請求的線程數量)的僞異步I/O模型(N 可以遠遠大於 M),下面一節"僞異步 BIO"中會詳細介紹到。

**我們再設想一下當客戶端併發訪問量增加後這種模型會出現什麼問題?**

在 Java 虛擬機中,線程是寶貴的資源,線程的創建和銷燬成本很高,除此之外,線程的切換成本也是很高的。尤其在 Linux 這樣的操作系統中,線程本質上就是一個進程,創建和銷燬線程都是重量級的系統函數。如果併發訪問量增加會導致線程數急劇膨脹可能會導致線程堆棧溢出、創建新線程失敗等問題,最終導致進程宕機或者僵死,不能對外提供服務。

 

1.2 僞異步 IO

爲了解決同步阻塞I/O面臨的一個鏈路需要一個線程處理的問題,後來有人對它的線程模型進行了優化一一一後端通過一個線程池來處理多個客戶端的請求接入,形成客戶端個數M:線程池最大線程數N的比例關係,其中M可以遠遠大於N.通過線程池可以靈活地調配線程資源,設置線程的最大值,防止由於海量併發接入導致線程耗盡。

採用線程池和任務隊列可以實現一種叫做僞異步的 I/O 通信框架,它的模型圖如上圖所示。當有新的客戶端接入時,將客戶端的 Socket 封裝成一個Task(該任務實現java.lang.Runnable接口)投遞到後端的線程池中進行處理,JDK 的線程池維護一個消息隊列和 N 個活躍線程,對消息隊列中的任務進行處理。由於線程池可以設置消息隊列的大小和最大線程數,因此,它的資源佔用是可控的,無論多少個客戶端併發訪問,都不會導致資源的耗盡和宕機。

僞異步I/O通信框架採用了線程池實現,因此避免了爲每個請求都創建一個獨立線程造成的線程資源耗盡問題。不過因爲它的底層任然是同步阻塞的BIO模型,因此無法從根本上解決問題。

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章