IO的方式通常分為幾種,同步阻塞的BIO、同步非阻塞的NIO、異步非阻塞的AIO。
一、BIO
在JDK1.4出來(lái)之前,我們建立網(wǎng)絡(luò )連接的時(shí)候采用BIO模式,需要先在服務(wù)端啟動(dòng)一個(gè)ServerSocket,然后在客戶(hù)端啟動(dòng)Socket來(lái)對服務(wù)端進(jìn)行通信,默認情況下服務(wù)端需要對每個(gè)請求建立一堆線(xiàn)程等待請求,而客戶(hù)端發(fā)送請求后,先咨詢(xún)服務(wù)端是否有線(xiàn)程相應,如果沒(méi)有則會(huì )一直等待或者遭到拒絕請求,如果有的話(huà),客戶(hù)端會(huì )線(xiàn)程會(huì )等待請求結束后才繼續執行。
二、NIO
NIO本身是基于事件驅動(dòng)思想來(lái)完成的,其主要想解決的是BIO的大并發(fā)問(wèn)題: 在使用同步I/O的網(wǎng)絡(luò )應用中,如果要同時(shí)處理多個(gè)客戶(hù)端請求,或是在客戶(hù)端要同時(shí)和多個(gè)服務(wù)器進(jìn)行通訊,就必須使用多線(xiàn)程來(lái)處理。也就是說(shuō),將每一個(gè)客戶(hù)端請求分配給一個(gè)線(xiàn)程來(lái)單獨處理。這樣做雖然可以達到我們的要求,但同時(shí)又會(huì )帶來(lái)另外一個(gè)問(wèn)題。由于每創(chuàng )建一個(gè)線(xiàn)程,就要為這個(gè)線(xiàn)程分配一定的內存空間(也叫工作存儲器),而且操作系統本身也對線(xiàn)程的總數有一定的限制。如果客戶(hù)端的請求過(guò)多,服務(wù)端程序可能會(huì )因為不堪重負而拒絕客戶(hù)端的請求,甚至服務(wù)器可能會(huì )因此而癱瘓。
NIO基于Reactor,當socket有流可讀或可寫(xiě)入socket時(shí),操作系統會(huì )相應的通知引用程序進(jìn)行處理,應用再將流讀取到緩沖區或寫(xiě)入操作系統。 也就是說(shuō),這個(gè)時(shí)候,已經(jīng)不是一個(gè)連接就要對應一個(gè)處理線(xiàn)程了,而是有效的請求,對應一個(gè)線(xiàn)程,當連接沒(méi)有數據時(shí),是沒(méi)有工作線(xiàn)程來(lái)處理的。
BIO與NIO一個(gè)比較重要的不同,是我們使用BIO的時(shí)候往往會(huì )引入多線(xiàn)程,每個(gè)連接一個(gè)單獨的線(xiàn)程;而NIO則是使用單線(xiàn)程或者只使用少量的多線(xiàn)程,每個(gè)連接共用一個(gè)線(xiàn)程。

NIO的最重要的地方是當一個(gè)連接創(chuàng )建后,不需要對應一個(gè)線(xiàn)程,這個(gè)連接會(huì )被注冊到多路復用器上面,所以所有的連接只需要一個(gè)線(xiàn)程就可以搞定,當這個(gè)線(xiàn)程中的多路復用器進(jìn)行輪詢(xún)的時(shí)候,發(fā)現連接上有請求的話(huà),才開(kāi)啟一個(gè)線(xiàn)程進(jìn)行處理,也就是一個(gè)請求一個(gè)線(xiàn)程模式。
在NIO的處理方式中,當一個(gè)請求來(lái)的話(huà),開(kāi)啟線(xiàn)程進(jìn)行處理,可能會(huì )等待后端應用的資源(JDBC連接等),其實(shí)這個(gè)線(xiàn)程就被阻塞了,當并發(fā)上來(lái)的話(huà),還是會(huì )有BIO一樣的問(wèn)題。
HTTP/1.1出現后,有了Http長(cháng)連接,這樣除了超時(shí)和指明特定關(guān)閉的http header外,這個(gè)鏈接是一直打開(kāi)的狀態(tài)的,這樣在NIO處理中可以進(jìn)一步的進(jìn)化,在后端資源中可以實(shí)現資源池或者隊列,當請求來(lái)的話(huà),開(kāi)啟的線(xiàn)程把請求和請求數據傳送給后端資源池或者隊列里面就返回,并且在全局的地方保持住這個(gè)現場(chǎng)(哪個(gè)連接的哪個(gè)請求等),這樣前面的線(xiàn)程還是可以去接受其他的請求,而后端的應用的處理只需要執行隊列里面的就可以了,這樣請求處理和后端應用是異步的.當后端處理完,到全局地方得到現場(chǎng),產(chǎn)生響應,這個(gè)就實(shí)現了異步處理。
三、AIO
與NIO不同,當進(jìn)行讀寫(xiě)操作時(shí),只須直接調用API的read或write方法即可。這兩種方法均為異步的,對于讀操作而言,當有流可讀取時(shí),操作系統會(huì )將可讀的流傳入read方法的緩沖區,并通知應用程序;對于寫(xiě)操作而言,當操作系統將write方法傳遞的流寫(xiě)入完畢時(shí),操作系統主動(dòng)通知應用程序。 即可以理解為,read/write方法都是異步的,完成后會(huì )主動(dòng)調用回調函數。 在JDK1.7中,這部分內容被稱(chēng)作NIO.2,主要在java.nio.channels包下增加了下面四個(gè)異步通道:
其中的read/write方法,會(huì )返回一個(gè)帶回調函數的對象,當執行完讀取/寫(xiě)入操作后,直接調用回調函數。
BIO是一個(gè)連接一個(gè)線(xiàn)程。
NIO是一個(gè)請求一個(gè)線(xiàn)程。
AIO是一個(gè)有效請求一個(gè)線(xiàn)程。
先來(lái)個(gè)例子理解一下概念,以銀行取款為例:
Java對BIO、NIO、AIO的支持:
Java BIO : 同步并阻塞,服務(wù)器實(shí)現模式為一個(gè)連接一個(gè)線(xiàn)程,即客戶(hù)端有連接請求時(shí)服務(wù)器端就需要啟動(dòng)一個(gè)線(xiàn)程進(jìn)行處理,如果這個(gè)連接不做任何事情會(huì )造成不必要的線(xiàn)程開(kāi)銷(xiāo),當然可以通過(guò)線(xiàn)程池機制改善。
Java NIO : 同步非阻塞,服務(wù)器實(shí)現模式為一個(gè)請求一個(gè)線(xiàn)程,即客戶(hù)端發(fā)送的連接請求都會(huì )注冊到多路復用器上,多路復用器輪詢(xún)到連接有I/O請求時(shí)才啟動(dòng)一個(gè)線(xiàn)程進(jìn)行處理。
Java AIO(NIO.2) : 異步非阻塞,服務(wù)器實(shí)現模式為一個(gè)有效請求一個(gè)線(xiàn)程,客戶(hù)端的I/O請求都是由OS先完成了再通知服務(wù)器應用去啟動(dòng)線(xiàn)程進(jìn)行處理,
BIO、NIO、AIO適用場(chǎng)景分析:
BIO方式適用于連接數目比較小且固定的架構,這種方式對服務(wù)器資源要求比較高,并發(fā)局限于應用中,JDK1.4以前的唯一選擇,但程序直觀(guān)簡(jiǎn)單易理解。
NIO方式適用于連接數目多且連接比較短(輕操作)的架構,比如聊天服務(wù)器,并發(fā)局限于應用中,編程比較復雜,JDK1.4開(kāi)始支持。
AIO方式使用于連接數目多且連接比較長(cháng)(重操作)的架構,比如相冊服務(wù)器,充分調用OS參與并發(fā)操作,編程比較復雜,JDK7開(kāi)始支持。
另外,I/O屬于底層操作,需要操作系統支持,并發(fā)也需要操作系統的支持,所以性能方面不同操作系統差異會(huì )比較明顯。
在高性能的I/O設計中,有兩個(gè)比較著(zhù)名的模式Reactor和Proactor模式,其中Reactor模式用于同步I/O,而Proactor運用于異步I/O操作。
在比較這兩個(gè)模式之前,我們首先的搞明白幾個(gè)概念,什么是阻塞和非阻塞,什么是同步和異步,同步和異步是針對應用程序和內核的交互而言的,同步指的是用戶(hù)進(jìn)程觸發(fā)IO操作并等待或者輪詢(xún)的去查看IO操作是否就緒,而異步是指用戶(hù)進(jìn)程觸發(fā)IO操作以后便開(kāi)始做自己的事情,而當IO操作已經(jīng)完成的時(shí)候會(huì )得到IO完成的通知。而阻塞和非阻塞是針對于進(jìn)程在訪(fǎng)問(wèn)數據的時(shí)候,根據IO操作的就緒狀態(tài)來(lái)采取的不同方式,說(shuō)白了是一種讀取或者寫(xiě)入操作函數的實(shí)現方式,阻塞方式下讀取或者寫(xiě)入函數將一直等待,而非阻塞方式下,讀取或者寫(xiě)入函數會(huì )立即返回一個(gè)狀態(tài)值。
一般來(lái)說(shuō)I/O模型可以分為:同步阻塞,同步非阻塞,異步阻塞,異步非阻塞IO
同步阻塞IO:在此種方式下,用戶(hù)進(jìn)程在發(fā)起一個(gè)IO操作以后,必須等待IO操作的完成,只有當真正完成了IO操作以后,用戶(hù)進(jìn)程才能運行。JAVA傳統的IO模型屬于此種方式!
同步非阻塞IO:在此種方式下,用戶(hù)進(jìn)程發(fā)起一個(gè)IO操作以后邊可返回做其它事情,但是用戶(hù)進(jìn)程需要時(shí)不時(shí)的詢(xún)問(wèn)IO操作是否就緒,這就要求用戶(hù)進(jìn)程不停的去詢(xún)問(wèn),從而引入不必要的CPU資源浪費。其中目前JAVA的NIO就屬于同步非阻塞IO。
異步阻塞IO:此種方式下是指應用發(fā)起一個(gè)IO操作以后,不等待內核IO操作的完成,等內核完成IO操作以后會(huì )通知應用程序,這其實(shí)就是同步和異步最關(guān)鍵的區別,同步必須等待或者主動(dòng)的去詢(xún)問(wèn)IO是否完成,那么為什么說(shuō)是阻塞的呢?因為此時(shí)是通過(guò)select系統調用來(lái)完成的,而select函數本身的實(shí)現方式是阻塞的,而采用select函數有個(gè)好處就是它可以同時(shí)監聽(tīng)多個(gè)文件句柄,從而提高系統的并發(fā)性!
異步非阻塞IO:在此種模式下,用戶(hù)進(jìn)程只需要發(fā)起一個(gè)IO操作然后立即返回,等IO操作真正的完成以后,應用程序會(huì )得到IO操作完成的通知,此時(shí)用戶(hù)進(jìn)程只需要對數據進(jìn)行處理就好了,不需要進(jìn)行實(shí)際的IO讀寫(xiě)操作,因為真正的IO讀取或者寫(xiě)入操作已經(jīng)由內核完成了。目前Java中還沒(méi)有支持此種IO模型。
聯(lián)系客服