來源:ncoding
ncoding.com/2018/04/02/java/io.html
同步、異步、阻塞、非阻塞都是和I/O(輸入輸出)有關的概念,最簡單的檔案讀取就是I/O操作,而在檔案讀取這件事兒上,可以有多種方式,
本篇會先介紹一下I/O的基本概念,通過一個生活例子來分別解釋下這幾種I/O模型,以及Java支持的I/O模型,
基本概念
在解釋I/O模型之前,我先說明一下幾個作業系統的概念
檔案描述符fd
檔案描述符(file descriptor)是計算機科學中的一個術語,是一個用于表述指向檔案的參考的抽象化概念,
檔案描述符在形式上是一個非負整數,實際上,它是一個索引值,指向內核為每一個行程所維護的該行程打開檔案的記錄表, 當程式打開一個現有檔案或者創建一個新檔案時,內核向行程回傳一個檔案描述符, 在程式設計中,一些涉及底層的程式撰寫往往會圍繞著檔案描述符展開,檔案描述符這一概念往往只適用于UNIX、Linux這樣的作業系統,
快取I/O
快取I/O又被稱作標準I/O,大多數檔案系統的默認I/O操作都是快取I/O,在Linux的快取I/O機制中, 作業系統會將I/O的資料快取在檔案系統的頁快取中,也就是說,資料會先被拷貝到作業系統內核的緩沖區中, 然后才會從作業系統內核的緩沖區拷貝到應用程式的地址空間,
快取I/O的缺點是資料在傳輸程序中需要在應用程式地址空間和內核進行多次資料拷貝操作,這些資料拷貝操作所帶來的CPU以及記憶體開銷是非常大的,
下面我以一個生活中燒開水的例子來形象解釋一下同步、異步、阻塞、非阻塞概念,
同步和異步
說到燒水,我們都是通過熱水壺來燒水的,在很久之前,科技還沒有這么發達的時候,如果我們要燒水, 需要把水壺放到火爐上,我們通過觀察水壺內的水的沸騰程度來判斷水有沒有燒開,
隨著科技的發展,現在市面上的水壺都有了提醒功能,當我們把水壺插電之后,水壺水燒開之后會通過聲音提醒我們水開了,
對于燒水這件事兒來說,傳統水壺的燒水就是同步的,高科技水壺的燒水就是異步的,
同步請求
A呼叫B,B的處理是同步的,在處理完之前他不會通知A,只有處理完之后才會明確的通知A,
異步請求
A呼叫B,B的處理是異步的,B在接到請求后先告訴A我已經接到請求了,然后異步去處理,處理完之后通過回呼等方式再通知A,
所以說,同步和異步最大的區別就是被呼叫方的執行方式和回傳時機, 同步指的是被呼叫方做完事情之后再回傳,異步指的是被呼叫方先回傳,然后再做事情,做完之后再想辦法通知呼叫方,
阻塞和非阻塞
還是那個燒水的例子,當你把水放到水壺里面,按下開關后,你可以坐在水壺前面,別的事情什么都不做, 一直等著水燒好,你還可以先去客廳看電視,等著水開就好了,
對于你來說,坐在水壺前面等就是阻塞的,去客廳看電視等著水開就是非阻塞的,
阻塞請求
A呼叫B,A一直等著B的回傳,別的事情什么也不干,
非阻塞請求
A呼叫B,A不用一直等著B的回傳,先去忙別的事情了,
所以說,阻塞和非阻塞最大的區別就是在被呼叫方回傳結果之前的這段時間內,呼叫方是否一直等待, 阻塞指的是呼叫方一直等待別的事情什么都不做,非阻塞指的是呼叫方先去忙別的事情,
阻塞、非阻塞和同步、異步的區別
首先,前面已經提到過,阻塞、非阻塞和同步、異步其實針對的物件是不一樣的,
給我大聲念三遍下面的句子
阻塞、非阻塞說的是呼叫者,同步、異步說的是被呼叫者,
阻塞、非阻塞說的是呼叫者,同步、異步說的是被呼叫者,
阻塞、非阻塞說的是呼叫者,同步、異步說的是被呼叫者,
有人認為阻塞和同步是一回事兒,非阻塞和異步是一回事,但是這是不對的,
同步包含阻塞和非阻塞
我們是用傳統的水壺燒水,在水燒開之前我們一直做在水壺前面,等著水開,這就是阻塞的,
我們是用傳統的水壺燒水,在水燒開之前我們先去客廳看電視了,但是水壺不會主動通知我們, 需要我們時不時的去廚房看一下水有沒有燒開,這就是非阻塞的,
異步包含阻塞和非阻塞
我們是用帶有提醒功能的水壺燒水,在水燒發出提醒之前我們一直做在水壺前面,等著水開,這就是阻塞的,
我們是用帶有提醒功能的水壺燒水,在水燒發出提醒之前我們先去客廳看電視了,等水壺發出聲音提醒我們,這就是非阻塞的,推薦閱讀:46 道阿里巴巴 Java 面試題,你會幾道?
Unix中的五種I/O模型
對于一次I/O訪問(以read舉例),資料會先被拷貝到作業系統內核的緩沖區中,然后才會從作業系統內核的緩沖區拷貝到應用程式的地址空間, 所以說,當一個read操作發生時,它會經歷兩個階段:
第一階段:等待資料準備 (Waiting for the data to be ready),
第二階段:將資料從內核拷貝到行程中 (Copying the data from the kernel to the process),
對于socket流而言
第一階段:通常涉及等待網路上的資料分組到達,也就是被復制到內核的某個緩沖區,
第二階段:把資料從內核緩沖區復制到應用行程緩沖區,
Unix下五種I/O模型:
-
同步阻塞I/O
-
同步非阻塞I/O
-
I/O多路復用(select和poll)
-
信號驅動I/O(SIGIO)
-
異步非阻塞 IO
同步阻塞I/O
阻塞I/O下請求無法立即完成則保持阻塞,阻塞I/O分為如下兩個階段,
階段1:等待資料就緒,網路I/O的情況就是等待遠端資料陸續抵達,也就是網路資料被復制到內核快取區中,磁盤I/O的情況就是等待磁盤資料從磁盤上讀取到內核態記憶體中,
階段2:資料拷貝,出于系統安全,用戶態的程式沒有權限直接讀取內核態記憶體,因此內核負責把內核態記憶體中的資料拷貝一份到用戶態記憶體中,
這兩個階段必須都完成后才能繼續下一步操作

所以,blocking IO的特點就是在IO執行的兩個階段都被block了,
同步非阻塞I/O
就是階段1的時候用戶行程可選擇做其他事情,通過輪詢的方式看看內核緩沖區是否就緒,如果資料就緒,再去執行階段2,
也就是說非阻塞的recvform系統呼叫呼叫之后,行程并沒有被阻塞,內核馬上回傳給行程,如果資料還沒準備好, 此時會回傳一個error,行程在回傳之后,可以干點別的事情,然后再發起recvform系統呼叫,
重復上面的程序, 回圈往復的進行recvform系統呼叫,這個程序通常被稱之為輪詢,輪詢檢查內核資料,直到資料準備好, 再拷貝資料到行程,進行資料處理,需要注意,第2階段的拷貝資料整個程序,行程仍然是屬于阻塞的狀態,推薦閱讀:Java 8 開發的 4 大頂級技巧
在linux下,可以通過設定socket使其變為non-blocking,當對一個non-blocking socket執行讀操作時,流程如圖所示:

所以,nonblocking IO的特點是用戶行程需要不斷的主動詢問kernel資料好了沒有,
I/O多路復用
我這里只想重點解釋一下I/O多路復用這種模型,因為現在用的最多,很多地方也稱為事件驅動IO模型,只是叫法不同,意思都一個樣,
IO多路復用是指內核一旦發現行程指定的一個或者多個IO條件準備讀取,它就通知該行程,
目前支持I/O多路復用的系統呼叫有 select、pselect、poll、epoll,I/O多路復用就是通過一種機制,一個行程可以監視多個描述符, 一旦某個檔案描述符fd就緒(一般是讀就緒或者寫就緒),能夠通知程式進行相應的讀寫操作,
但select、pselect、poll、epoll本質上都是同步I/O,因為他們都需要在讀寫事件就緒后自己負責進行讀寫,也就是說這個讀寫程序是阻塞的, 而異步I/O則無需自己負責進行讀寫,異步I/O的實作會負責把資料從內核拷貝到用戶空間,
相比較于同步非阻塞I/O,它的改進的地方在于,原來需要用戶行程去輪詢的這事兒交給了內核執行緒幫你完成, 而且這個內核執行緒可以等待多個socket,能實作同時對多個IO埠進行監聽,

多路復用的特點是通過一種機制一個行程能同時等待IO檔案描述符,內核監視這些檔案描述符(套接字描述符), 其中的任意一個進入讀就緒狀態,select, poll,epoll函式就可以回傳,對于監視的方式, 又可以分為 select, poll, epoll三種方式,
所以,如果處理的連接數不是很高的話,使用select/epoll的web server不一定比使用多執行緒 + 阻塞IO的web server性能更好,可能延遲還更大, 也就是說,select/epoll的優勢并不是對于單個連接能處理得更快,而是在于能處理更多的連接,推薦閱讀:Spring Boot 的 10 個核心模塊
高并發的程式一般使用同步非阻塞方式而非多執行緒 + 同步阻塞方式,要理解這一點,首先要扯到并發和并行的區別, 比如去某部門辦事需要依次去幾個視窗,辦事大廳里的人數就是并發數,而視窗個數就是并行度, 也就是說并發數是指同時進行的任務數(如同時服務的 HTTP 請求),而并行數是可以同時作業的物理資源數量(如 CPU 核數),
通過合理調度任務的不同階段,并發數可以遠遠大于并行度,這就是區區幾個 CPU 可以支持上萬個用戶并發請求的奧秘, 在這種高并發的情況下,為每個任務(用戶請求)創建一個行程或執行緒的開銷非常大,而同步非阻塞方式可以把多個 IO 請求丟到后臺去, 這就可以在一個行程里服務大量的并發 IO 請求,
IO多路復用歸為同步阻塞模式
異步非阻塞 IO
相對于同步IO,異步IO不是順序執行,用戶行程進行aio_read系統呼叫之后,無論內核資料是否準備好,都會直接回傳給用戶行程, 然后用戶態行程可以去做別的事情,等到socket資料準備好了,內核直接復制資料給行程,然后從內核向行程發送通知,IO兩個階段, 行程都是非阻塞的,
Linux提供了AIO庫函式實作異步,但是用的很少,目前有很多開源的異步IO庫,例如libevent、libev、libuv,異步程序如下圖所示:

更詳細的分析可參考 聊聊Linux5種IO模型
Java中四種I/O模型
上一章所述Unix中的五種I/O模型,除信號驅動I/O外,Java對其它四種I/O模型都有所支持,
-
Java傳統IO模型即是同步阻塞I/O
-
NIO是同步非阻塞I/O
-
通過NIO實作的Reactor模式即是I/O多路復用模型的實作
-
通過AIO實作的Proactor模式即是異步I/O模型的實作
關注公眾號Java技術堆疊回復"面試"獲取我整理的2020最全面試題及答案,
推薦去我的博客閱讀更多:
1.Java JVM、集合、多執行緒、新特性系列教程
2.Spring MVC、Spring Boot、Spring Cloud 系列教程
3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程
4.Java、后端、架構、阿里巴巴等大廠最新面試題
覺得不錯,別忘了點贊+轉發哦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/153362.html
標籤:Java
