IO: BIO ? NIO ? AIO?
IO的方式通常分為幾種,同步阻塞的BIO、同步非阻塞的NIO、異步非阻塞的AIO。
一、BIO
?????在JDK1.4出來(lái)之前,我們建立網(wǎng)絡(luò)連接的時(shí)候采用BIO模式,需要先在服務(wù)端啟動(dòng)一個(gè)ServerSocket,然后在客戶端啟動(dòng)Socket來(lái)對(duì)服務(wù)端進(jìn)行通信,默認(rèn)情況下服務(wù)端需要對(duì)每個(gè)請(qǐng)求建立一堆線程等待請(qǐng)求,而客戶端發(fā)送請(qǐng)求后,先咨詢服務(wù)端是否有線程相應(yīng),如果沒(méi)有則會(huì)一直等待或者遭到拒絕請(qǐng)求,如果有的話,客戶端會(huì)線程會(huì)等待請(qǐng)求結(jié)束后才繼續(xù)執(zhí)行。
二、NIO
????NIO本身是基于事件驅(qū)動(dòng)思想來(lái)完成的,其主要想解決的是BIO的大并發(fā)問(wèn)題: 在使用同步I/O的網(wǎng)絡(luò)應(yīng)用中,如果要同時(shí)處理多個(gè)客戶端請(qǐng)求,或是在客戶端要同時(shí)和多個(gè)服務(wù)器進(jìn)行通訊,就必須使用多線程來(lái)處理。也就是說(shuō),將每一個(gè)客戶端請(qǐng)求分配給一個(gè)線程來(lái)單獨(dú)處理。這樣做雖然可以達(dá)到我們的要求,但同時(shí)又會(huì)帶來(lái)另外一個(gè)問(wèn)題。由于每創(chuàng)建一個(gè)線程,就要為這個(gè)線程分配一定的內(nèi)存空間(也叫工作存儲(chǔ)器),而且操作系統(tǒng)本身也對(duì)線程的總數(shù)有一定的限制。如果客戶端的請(qǐng)求過(guò)多,服務(wù)端程序可能會(huì)因?yàn)椴豢爸刎?fù)而拒絕客戶端的請(qǐng)求,甚至服務(wù)器可能會(huì)因此而癱瘓。
??? NIO基于Reactor,當(dāng)socket有流可讀或可寫入socket時(shí),操作系統(tǒng)會(huì)相應(yīng)的通知應(yīng)用程序進(jìn)行處理,應(yīng)用再將流讀取到緩沖區(qū)或?qū)懭氩僮飨到y(tǒng)。? 也就是說(shuō),這個(gè)時(shí)候,已經(jīng)不是一個(gè)連接就要對(duì)應(yīng)一個(gè)處理線程了,而是有效的請(qǐng)求,對(duì)應(yīng)一個(gè)線程,當(dāng)連接沒(méi)有數(shù)據(jù)時(shí),是沒(méi)有工作線程來(lái)處理的。
?? BIO與NIO一個(gè)比較重要的不同,是我們使用BIO的時(shí)候往往會(huì)引入多線程,每個(gè)連接一個(gè)單獨(dú)的線程;而NIO則是使用單線程或者只使用少量的多線程,每個(gè)連接共用一個(gè)線程。(IO多路復(fù)用技術(shù))
????? NIO的最重要的地方是當(dāng)一個(gè)連接創(chuàng)建后,不需要對(duì)應(yīng)一個(gè)線程,這個(gè)連接會(huì)被注冊(cè)到多路復(fù)用器上面,所以所有的連接只需要一個(gè)線程就可以搞定,當(dāng)這個(gè)線程中的多路復(fù)用器進(jìn)行輪詢的時(shí)候,發(fā)現(xiàn)連接上有請(qǐng)求的話,才開(kāi)啟一個(gè)線程進(jìn)行處理,也就是一個(gè)請(qǐng)求一個(gè)線程模式。
??????在NIO的處理方式中,當(dāng)一個(gè)請(qǐng)求來(lái)的話,開(kāi)啟線程進(jìn)行處理,可能會(huì)等待后端應(yīng)用的資源(JDBC連接等),其實(shí)這個(gè)線程就被阻塞了,當(dāng)并發(fā)上來(lái)的話,還是會(huì)有BIO一樣的問(wèn)題。
HTTP/1.1出現(xiàn)后,有了Http長(zhǎng)連接,這樣除了超時(shí)和指明特定關(guān)閉的http header外,這個(gè)鏈接是一直打開(kāi)的狀態(tài)的,這樣在NIO處理中可以進(jìn)一步的進(jìn)化,在后端資源中可以實(shí)現(xiàn)資源池或者隊(duì)列,當(dāng)請(qǐng)求來(lái)的話,開(kāi)啟的線程把請(qǐng)求和請(qǐng)求數(shù)據(jù)傳送給后端資源池或者隊(duì)列里面就返回(異步隊(duì)列),并且在全局的地方保持住這個(gè)現(xiàn)場(chǎng)(哪個(gè)連接的哪個(gè)請(qǐng)求等),這樣前面的線程還是可以去接受其他的請(qǐng)求,而后端的應(yīng)用的處理只需要執(zhí)行隊(duì)列里面的就可以了,這樣請(qǐng)求處理和后端應(yīng)用是異步的.當(dāng)后端處理完,到全局地方得到現(xiàn)場(chǎng),產(chǎn)生響應(yīng),這個(gè)就實(shí)現(xiàn)了異步處理。
三、AIO
???? 與NIO不同,當(dāng)進(jìn)行讀寫操作時(shí),只須直接調(diào)用API的read或write方法即可。這兩種方法均為異步的,對(duì)于讀操作而言,當(dāng)有流可讀取時(shí),操作系統(tǒng)會(huì)將可讀的流傳入read方法的緩沖區(qū),并通知應(yīng)用程序;對(duì)于寫操作而言,當(dāng)操作系統(tǒng)將write方法傳遞的流寫入完畢時(shí),操作系統(tǒng)主動(dòng)通知應(yīng)用程序。? 即可以理解為,read/write方法都是異步的,完成后會(huì)主動(dòng)調(diào)用回調(diào)函數(shù)。? 在JDK1.7中,這部分內(nèi)容被稱作NIO.2,主要在java.nio.channels包下增加了下面四個(gè)異步通道:
- AsynchronousSocketChannel
- AsynchronousServerSocketChannel
- AsynchronousFileChannel
- AsynchronousDatagramChannel
其中的read/write方法,會(huì)返回一個(gè)帶回調(diào)函數(shù)的對(duì)象,當(dāng)執(zhí)行完讀取/寫入操作后,直接調(diào)用回調(diào)函數(shù)。
BIO是一個(gè)連接一個(gè)線程。
NIO是一個(gè)請(qǐng)求一個(gè)線程。
AIO是一個(gè)有效請(qǐng)求一個(gè)線程。
先來(lái)個(gè)例子理解一下概念,以銀行取款為例:?
- 同步 : 自己親自出馬持銀行卡到銀行取錢(使用同步IO時(shí),Java自己處理IO讀寫);
- 異步 : 委托一小弟拿銀行卡到銀行取錢,然后給你(使用異步IO時(shí),Java將IO讀寫委托給OS處理,需要將數(shù)據(jù)緩沖區(qū)地址和大小傳給OS(銀行卡和密碼),OS需要支持異步IO操作API);
- 阻塞 : ATM排隊(duì)取款,你只能等待(使用阻塞IO時(shí),Java調(diào)用會(huì)一直阻塞到讀寫完成才返回);
- 非阻塞 : 柜臺(tái)取款,取個(gè)號(hào),然后坐在椅子上做其它事,等號(hào)廣播會(huì)通知你辦理,沒(méi)到號(hào)你就不能去,你可以不斷問(wèn)大堂經(jīng)理排到了沒(méi)有,大堂經(jīng)理如果說(shuō)還沒(méi)到你就不能去(使用非阻塞IO時(shí),如果不能讀寫Java調(diào)用會(huì)馬上返回,當(dāng)IO事件分發(fā)器會(huì)通知可讀寫時(shí)再繼續(xù)進(jìn)行讀寫,不斷循環(huán)直到讀寫完成)
Java對(duì)BIO、NIO、AIO的支持:
-
Java BIO : 同步并阻塞,服務(wù)器實(shí)現(xiàn)模式為一個(gè)連接一個(gè)線程,即客戶端有連接請(qǐng)求時(shí)服務(wù)器端就需要啟動(dòng)一個(gè)線程進(jìn)行處理,如果這個(gè)連接不做任何事情會(huì)造成不必要的線程開(kāi)銷,當(dāng)然可以通過(guò)線程池機(jī)制改善。
-
Java NIO : 同步非阻塞,服務(wù)器實(shí)現(xiàn)模式為一個(gè)請(qǐng)求一個(gè)線程,即客戶端發(fā)送的連接請(qǐng)求都會(huì)注冊(cè)到多路復(fù)用器上,多路復(fù)用器輪詢到連接有I/O請(qǐng)求時(shí)才啟動(dòng)一個(gè)線程進(jìn)行處理。
-
Java AIO(NIO.2) : 異步非阻塞,服務(wù)器實(shí)現(xiàn)模式為一個(gè)有效請(qǐng)求一個(gè)線程,客戶端的I/O請(qǐng)求都是由OS先完成了再通知服務(wù)器應(yīng)用去啟動(dòng)線程進(jìn)行處理,
BIO、NIO、AIO適用場(chǎng)景分析:
-
BIO方式適用于連接數(shù)目比較小且固定的架構(gòu),這種方式對(duì)服務(wù)器資源要求比較高,并發(fā)局限于應(yīng)用中,JDK1.4以前的唯一選擇,但程序直觀簡(jiǎn)單易理解。
-
NIO方式適用于連接數(shù)目多且連接比較短(輕操作)的架構(gòu),比如聊天服務(wù)器,并發(fā)局限于應(yīng)用中,編程比較復(fù)雜,JDK1.4開(kāi)始支持。
-
AIO方式使用于連接數(shù)目多且連接比較長(zhǎng)(重操作)的架構(gòu),比如相冊(cè)服務(wù)器,充分調(diào)用OS參與并發(fā)操作,編程比較復(fù)雜,JDK7開(kāi)始支持。
另外,I/O屬于底層操作,需要操作系統(tǒng)支持,并發(fā)也需要操作系統(tǒng)的支持,所以性能方面不同操作系統(tǒng)差異會(huì)比較明顯。
在高性能的I/O設(shè)計(jì)中,有兩個(gè)比較著名的模式Reactor和Proactor模式,其中Reactor模式用于同步I/O,而Proactor運(yùn)用于異步I/O操作。
?一般來(lái)說(shuō)I/O模型可以分為:同步阻塞,同步非阻塞,異步阻塞,異步非阻塞IO
1,同步和異步是針對(duì)應(yīng)用程序和內(nèi)核的交互而言的。?同步/異步是在時(shí)間上強(qiáng)調(diào)處理事情的結(jié)果/機(jī)會(huì)成本的兩種處理策略;強(qiáng)調(diào)結(jié)果意味著對(duì)結(jié)果的迫不急待,不過(guò)結(jié)果是正確的還是錯(cuò)誤的,反正你要立即給我一個(gè)結(jié)果響應(yīng);強(qiáng)調(diào)時(shí)間機(jī)會(huì)成本意味著對(duì)等待結(jié)果浪費(fèi)的時(shí)間極其難接受,而對(duì)結(jié)果并不是那么急切,暫時(shí)不管結(jié)果(讓處理方處理完主動(dòng)通知結(jié)果/自己空閑的時(shí)候主動(dòng)去獲取結(jié)果)轉(zhuǎn)而去處理其他事情
2,阻塞和非阻塞是針對(duì)于進(jìn)程在訪問(wèn)數(shù)據(jù)的時(shí)候,根據(jù)IO操作的就緒狀態(tài)來(lái)采取的不同方式,說(shuō)白了是一種讀取或者寫入操作函數(shù)的實(shí)現(xiàn)方式,阻塞方式下讀取或者寫入函數(shù)將一直等待,而非阻塞方式下,讀取或者寫入函數(shù)會(huì)立即返回一個(gè)狀態(tài)值。
3,同步/異步是宏觀上(進(jìn)程間通訊,通常表現(xiàn)為網(wǎng)絡(luò)IO的處理上),阻塞/非阻塞是微觀上(進(jìn)程內(nèi)數(shù)據(jù)傳輸,通常表現(xiàn)為對(duì)本地IO的處理上);阻塞和非阻塞是同步/異步的表現(xiàn)形式
由上描述基本可以總結(jié)一句簡(jiǎn)短的話,同步和異步是目的,阻塞和非阻塞是實(shí)現(xiàn)方式。?
?
下面我們?cè)賮?lái)理解組合方式的IO類型,就好理解多了。?
同步阻塞IO(JAVA BIO):?
??? 同步并阻塞,服務(wù)器實(shí)現(xiàn)模式為一個(gè)連接一個(gè)線程,即客戶端有連接請(qǐng)求時(shí)服務(wù)器端就需要啟動(dòng)一個(gè)線程進(jìn)行處理,如果這個(gè)連接不做任何事情會(huì)造成不必要的線程開(kāi)銷,當(dāng)然可以通過(guò)線程池機(jī)制改善。?
同步非阻塞IO(Java NIO) : 同步非阻塞,服務(wù)器實(shí)現(xiàn)模式為一個(gè)請(qǐng)求一個(gè)線程,即客戶端發(fā)送的連接請(qǐng)求都會(huì)注冊(cè)到多路復(fù)用器上,多路復(fù)用器輪詢到連接有I/O請(qǐng)求時(shí)才啟動(dòng)一個(gè)線程進(jìn)行處理。用戶進(jìn)程也需要時(shí)不時(shí)的詢問(wèn)IO操作是否就緒,這就要求用戶進(jìn)程不停的去詢問(wèn)。?
異步阻塞IO(Java NIO):??
?? 此種方式下是指應(yīng)用發(fā)起一個(gè)IO操作以后,不等待內(nèi)核IO操作的完成,等內(nèi)核完成IO操作以后會(huì)通知應(yīng)用程序,這其實(shí)就是同步和異步最關(guān)鍵的區(qū)別,同步必須等待或者主動(dòng)的去詢問(wèn)IO是否完成,那么為什么說(shuō)是阻塞的呢?因?yàn)榇藭r(shí)是通過(guò)select系統(tǒng)調(diào)用來(lái)完成的,而select函數(shù)本身的實(shí)現(xiàn)方式是阻塞的,而采用select函數(shù)有個(gè)好處就是它可以同時(shí)監(jiān)聽(tīng)多個(gè)文件句柄(如果從UNP的角度看,select屬于同步操作。因?yàn)閟elect之后,進(jìn)程還需要讀寫數(shù)據(jù)),從而提高系統(tǒng)的并發(fā)性!??
(Java AIO(NIO.2))異步非阻塞IO:??
?? 在此種模式下,用戶進(jìn)程只需要發(fā)起一個(gè)IO操作然后立即返回,等IO操作真正的完成以后,應(yīng)用程序會(huì)得到IO操作完成的通知,此時(shí)用戶進(jìn)程只需要對(duì)數(shù)據(jù)進(jìn)行處理就好了,不需要進(jìn)行實(shí)際的IO讀寫操作,因?yàn)檎嬲腎O讀取或者寫入操作已經(jīng)由內(nèi)核完成了。????
BIO、NIO、AIO適用場(chǎng)景分析:?
??? BIO方式適用于連接數(shù)目比較小且固定的架構(gòu),這種方式對(duì)服務(wù)器資源要求比較高,并發(fā)局限于應(yīng)用中,JDK1.4以前的唯一選擇,但程序直觀簡(jiǎn)單易理解。?
??? NIO方式適用于連接數(shù)目多且連接比較短(輕操作)的架構(gòu),比如聊天服務(wù)器,并發(fā)局限于應(yīng)用中,編程比較復(fù)雜,JDK1.4開(kāi)始支持。?
??? AIO方式使用于連接數(shù)目多且連接比較長(zhǎng)(重操作)的架構(gòu),比如相冊(cè)服務(wù)器,充分調(diào)用OS參與并發(fā)操作,編程比較復(fù)雜,JDK7開(kāi)始支持。?
搞清楚了以上概念以后,我們?cè)倩剡^(guò)頭來(lái)看看,Reactor模式和Proactor模式。??
?
?首先來(lái)看看Reactor模式,Reactor模式應(yīng)用于同步I/O的場(chǎng)景。我們分別以讀操作和寫操作為例來(lái)看看Reactor中的具體步驟:??
讀取操作:??
1. 應(yīng)用程序注冊(cè)讀就緒事件和相關(guān)聯(lián)的事件處理器??
??
2. 事件分離器等待事件的發(fā)生??
??
3. 當(dāng)發(fā)生讀就緒事件的時(shí)候,事件分離器調(diào)用第一步注冊(cè)的事件處理器??
??
4. 事件處理器首先執(zhí)行實(shí)際的讀取操作,然后根據(jù)讀取到的內(nèi)容進(jìn)行進(jìn)一步的處理??
??
寫入操作類似于讀取操作,只不過(guò)第一步注冊(cè)的是寫就緒事件。??
???
??
下面我們來(lái)看看Proactor模式中讀取操作和寫入操作的過(guò)程:??
讀取操作:??
1. 應(yīng)用程序初始化一個(gè)異步讀取操作,然后注冊(cè)相應(yīng)的事件處理器,此時(shí)事件處理器不關(guān)注讀取就緒事件,而是關(guān)注讀取完成事件,這是區(qū)別于Reactor的關(guān)鍵。??
??
2. 事件分離器等待讀取操作完成事件??
??
3. 在事件分離器等待讀取操作完成的時(shí)候,操作系統(tǒng)調(diào)用內(nèi)核線程完成讀取操作(異步IO都是操作系統(tǒng)負(fù)責(zé)將數(shù)據(jù)讀寫到應(yīng)用傳遞進(jìn)來(lái)的緩沖區(qū)供應(yīng)用程序操作,操作系統(tǒng)扮演了重要角色),并將讀取的內(nèi)容放入用戶傳遞過(guò)來(lái)的緩存區(qū)中。這也是區(qū)別于Reactor的一點(diǎn),Proactor中,應(yīng)用程序需要傳遞緩存區(qū)。??
??
4. 事件分離器捕獲到讀取完成事件后,激活應(yīng)用程序注冊(cè)的事件處理器,事件處理器直接從緩存區(qū)讀取數(shù)據(jù),而不需要進(jìn)行實(shí)際的讀取操作。??
??
Proactor中寫入操作和讀取操作,只不過(guò)感興趣的事件是寫入完成事件。??
??
從上面可以看出,Reactor和Proactor模式的主要區(qū)別就是真正的讀取和寫入操作是有誰(shuí)來(lái)完成的,Reactor中需要應(yīng)用程序自己讀取或者寫入數(shù)據(jù),而Proactor模式中,應(yīng)用程序不需要進(jìn)行實(shí)際的讀寫過(guò)程,它只需要從緩存區(qū)讀取或者寫入即可,操作系統(tǒng)會(huì)讀取緩存區(qū)或者寫入緩存區(qū)到真正的IO設(shè)備.??
???
????????? 綜上所述,同步和異步是相對(duì)于應(yīng)用和內(nèi)核的交互方式而言的,同步 需要主動(dòng)去詢問(wèn),而異步的時(shí)候內(nèi)核在IO事件發(fā)生的時(shí)候通知應(yīng)用程序,而阻塞和非阻塞僅僅是系統(tǒng)在調(diào)用系統(tǒng)調(diào)用的時(shí)候函數(shù)的實(shí)現(xiàn)方式而已。??
如果你想吃一份宮保雞丁蓋飯:?
同步阻塞:你到飯館點(diǎn)餐,然后在那等著,還要一邊喊:好了沒(méi)啊!?
同步非阻塞:在飯館點(diǎn)完餐,就去遛狗了。不過(guò)溜一會(huì)兒,就回飯館喊一聲:好了沒(méi)啊!?
異步阻塞:遛狗的時(shí)候,接到飯館電話,說(shuō)飯做好了,讓您親自去拿。?
異步非阻塞:飯館打電話說(shuō),我們知道您的位置,一會(huì)給你送過(guò)來(lái),安心遛狗就可以了。?
“一個(gè)IO操作其實(shí)分成了兩個(gè)步驟:發(fā)起IO請(qǐng)求和實(shí)際的IO操作。?
同步IO和異步IO的區(qū)別就在于第二個(gè)步驟是否阻塞,如果實(shí)際的IO讀寫阻塞請(qǐng)求進(jìn)程,那么就是同步IO。?
阻塞IO和非阻塞IO的區(qū)別在于第一步,發(fā)起IO請(qǐng)求是否會(huì)被阻塞,如果阻塞直到完成那么就是傳統(tǒng)的阻塞IO,如果不阻塞,那么就是非阻塞IO。?
同步和異步是針對(duì)應(yīng)用程序和內(nèi)核的交互而言的,同步指的是用戶進(jìn)程觸發(fā)IO操作并等待或者輪詢的去查看IO操作是否就緒,而異步是指用戶進(jìn)程觸發(fā)IO操作以后便開(kāi)始做自己的事情,而當(dāng)IO操作已經(jīng)完成的時(shí)候會(huì)得到IO完成的通知。而阻塞和非阻塞是針對(duì)于進(jìn)程在訪問(wèn)數(shù)據(jù)的時(shí)候,根據(jù)IO操作的就緒狀態(tài)來(lái)采取的不同方式,說(shuō)白了是一種讀取或者寫入操作函數(shù)的實(shí)現(xiàn)方式,阻塞方式下讀取或者寫入函數(shù)將一直等待,而非阻塞方式下,讀取或者寫入函數(shù)會(huì)立即返回一個(gè)狀態(tài)值。?
所以,IO操作可以分為3類:同步阻塞(即早期的IO操作)、同步非阻塞(NIO)、異步(AIO)。?
同步阻塞:?
在此種方式下,用戶進(jìn)程在發(fā)起一個(gè)IO操作以后,必須等待IO操作的完成,只有當(dāng)真正完成了IO操作以后,用戶進(jìn)程才能運(yùn)行。JAVA傳統(tǒng)的IO模型屬于此種方式。?
同步非阻塞:?
在此種方式下,用戶進(jìn)程發(fā)起一個(gè)IO操作以后邊可返回做其它事情,但是用戶進(jìn)程需要時(shí)不時(shí)的詢問(wèn)IO操作是否就緒,這就要求用戶進(jìn)程不停的去詢問(wèn),從而引入不必要的CPU資源浪費(fèi)。其中目前JAVA的NIO就屬于同步非阻塞IO。?
異步:?
此種方式下是指應(yīng)用發(fā)起一個(gè)IO操作以后,不等待內(nèi)核IO操作的完成,等內(nèi)核完成IO操作以后會(huì)通知應(yīng)用程序?!?
最后,幾篇解釋的不錯(cuò)的文章:
BIO NIO AIO
NIO.2 入門,第 1 部分: 異步通道 API
使用異步 I/O 大大提高應(yīng)用程序的性能
?
?
總結(jié)
以上是生活随笔為你收集整理的IO: BIO ? NIO ? AIO?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 数据库理论复习点
- 下一篇: Windows下打包maven项目,编写