網(wǎng)站 優(yōu)化手機(jī)版網(wǎng)絡(luò)優(yōu)化大師手機(jī)版
Java流的詳細(xì)解讀BIO VS NIO VS AIO
BIO
BIO:傳統(tǒng)的網(wǎng)絡(luò)通訊模型,就是BIO,同步阻塞IO。
它其實(shí)就是服務(wù)端創(chuàng)建一個(gè)ServerSocket, 然后就是客戶端用一個(gè)Socket去連接服務(wù)端的那個(gè)ServerSocket, ServerSocket接收到了一個(gè)的連接請(qǐng)求就創(chuàng)建一個(gè)Socket和一個(gè)線程去跟那個(gè)Socket進(jìn)行通訊。
接著客戶端和服務(wù)端就進(jìn)行阻塞式的通信,客戶端發(fā)送一個(gè)請(qǐng)求,服務(wù)端Socket進(jìn)行處理后返回響應(yīng)。
在響應(yīng)返回前,客戶端那邊就阻塞等待,上門(mén)事情也做不了。
這種方式的缺點(diǎn):每次一個(gè)客戶端接入,都需要在服務(wù)端創(chuàng)建一個(gè)線程來(lái)服務(wù)這個(gè)客戶端。
這樣大量客戶端來(lái)的時(shí)候,就會(huì)造成服務(wù)端的線程數(shù)量可能達(dá)到了幾千甚至幾萬(wàn),這樣就可能會(huì)造成服務(wù)端過(guò)載過(guò)高,最后崩潰死掉。
BIO模型圖
Acceptor 線程模型
傳統(tǒng)的IO模型的網(wǎng)絡(luò)服務(wù)的設(shè)計(jì)模式中有倆種比較經(jīng)典的設(shè)計(jì)模式:一個(gè)是多線程, 一種是依靠線程池來(lái)進(jìn)行處理。
如果是基于多線程的模式來(lái)的話,就是這樣的模式,這種也是Acceptor線程模型。
NIO
NIO是一種同步非阻塞IO, 基于Reactor模型來(lái)實(shí)現(xiàn)的。
其實(shí)相當(dāng)于就是一個(gè)線程處理大量的客戶端的請(qǐng)求,通過(guò)一個(gè)線程輪詢大量的channel,每次就獲取一批有事件的channel,然后對(duì)每個(gè)請(qǐng)求啟動(dòng)一個(gè)線程處理即可。
這里的核心就是非阻塞,就那個(gè)selector一個(gè)線程就可以不停輪詢channel,所有客戶端請(qǐng)求都不會(huì)阻塞,直接就會(huì)進(jìn)來(lái),大不了就是等待一下排著隊(duì)而已。
這里面優(yōu)化BIO的核心就是,一個(gè)客戶端并不是時(shí)時(shí)刻刻都有數(shù)據(jù)進(jìn)行交互,沒(méi)有必要死耗著一個(gè)線程不放,所以客戶端選擇了讓線程歇一歇,只有客戶端有相應(yīng)的操作的時(shí)候才發(fā)起通知,創(chuàng)建一個(gè)線程來(lái)處理請(qǐng)求。
NIO 模型圖
Reactor模型
AIO
AIO:異步非阻塞IO,基于Proactor模型實(shí)現(xiàn)。
每個(gè)連接發(fā)送過(guò)來(lái)的請(qǐng)求,都會(huì)綁定一個(gè)Buffer,然后通知操作系統(tǒng)去完成異步的讀,這個(gè)時(shí)間你就可以去做其他的事情。
等到操作系統(tǒng)完成讀之后,就會(huì)調(diào)用你的接口,給你操作系統(tǒng)異步讀完的數(shù)據(jù)。這個(gè)時(shí)候你就可以拿到數(shù)據(jù)進(jìn)行處理,將數(shù)據(jù)往回寫(xiě)。
在往回寫(xiě)的過(guò)程,同樣是給操作系統(tǒng)一個(gè)Buffer,讓操作系統(tǒng)去完成寫(xiě),寫(xiě)完了來(lái)通知你。
這倆個(gè)過(guò)程都有buffer存在,數(shù)據(jù)都是通過(guò)buffer來(lái)完成讀寫(xiě)。
這里面的主要的區(qū)別在于將數(shù)據(jù)寫(xiě)入的緩沖區(qū)后,就不去管它,剩下的去交給操作系統(tǒng)去完成。
操作系統(tǒng)寫(xiě)回?cái)?shù)據(jù)也是一樣,寫(xiě)到Buffer里面,寫(xiě)完后通知客戶端來(lái)進(jìn)行讀取數(shù)據(jù)。
AIO模型
BIO、NIO、AIO三個(gè)模型的同步和阻塞問(wèn)題
同步阻塞
BIO是同步阻塞的,這里說(shuō)的不是針對(duì)網(wǎng)絡(luò)通訊模型而言,而是針對(duì)磁盤(pán)文件讀寫(xiě)IO操作來(lái)說(shuō)的。
因?yàn)橛肂IO的流讀寫(xiě)文件,例如FileInputStrem,是說(shuō)你發(fā)起個(gè)IO請(qǐng)求直接hang死,卡在那里,必須等著搞完了這次IO才能返回。
同步非阻塞
NIO為啥是同步非阻塞,因?yàn)闊o(wú)論多少客戶端都可以接入服務(wù)端,客戶端接入并不會(huì)耗費(fèi)一個(gè)線程,只會(huì)創(chuàng)建一個(gè)連接然后注冊(cè)到selector上去,這樣你就可以去干其他你想干的其他事情了。
一個(gè)selector線程不斷的輪詢所有的socket連接,發(fā)現(xiàn)有事件了就通知你,然后你就啟動(dòng)一個(gè)線程處理一個(gè)請(qǐng)求即可,這個(gè)過(guò)程的話就是非阻塞的。
但是這個(gè)處理的過(guò)程中,你還是要先讀取數(shù)據(jù),處理,再返回的,這是個(gè)同步的過(guò)程。
異步非阻塞
通過(guò)AIO發(fā)起個(gè)文件IO操作之后,你立馬就返回可以干別的事兒了,接下來(lái)你也不用管了,操作系統(tǒng)自己干完了IO之后,告訴你說(shuō)ok了。
當(dāng)你基于AIO的api去讀寫(xiě)文件時(shí), 當(dāng)你發(fā)起一個(gè)請(qǐng)求之后,剩下的事情就是交給了操作系統(tǒng)。
當(dāng)讀寫(xiě)完成后, 操作系統(tǒng)會(huì)來(lái)回調(diào)你的接口, 告訴你操作完成。
在這期間不需要等待, 也不需要去輪詢判斷操作系統(tǒng)完成的狀態(tài),你可以去干其他的事情。
同步就是自己還得主動(dòng)去輪詢操作系統(tǒng),異步就是操作系統(tǒng)反過(guò)來(lái)通知你。所以來(lái)說(shuō), AIO就是異步非阻塞的。
首先我們來(lái)了解下傳統(tǒng)的Socket網(wǎng)絡(luò)通訊模型。
NIO核心組件詳細(xì)講解
學(xué)習(xí)NIO先來(lái)搞清楚一些相關(guān)的概念,NIO通訊有哪些相關(guān)組件,對(duì)應(yīng)的作用都是什么,之間有哪些聯(lián)系?
多路復(fù)用機(jī)制實(shí)現(xiàn)Selector
首先我們來(lái)了解下傳統(tǒng)的Socket網(wǎng)絡(luò)通訊模型。
傳統(tǒng)Socket通訊原理圖
為什么傳統(tǒng)的socket不支持海量連接?
每次一個(gè)客戶端接入,都是要在服務(wù)端創(chuàng)建一個(gè)線程來(lái)服務(wù)這個(gè)客戶端的
這會(huì)導(dǎo)致大量的客戶端的時(shí)候,服務(wù)端的線程數(shù)量可能達(dá)到幾千甚至幾萬(wàn),幾十萬(wàn),這會(huì)導(dǎo)致服務(wù)器端程序負(fù)載過(guò)高,不堪重負(fù),最終系統(tǒng)崩潰死掉。
接著來(lái)看下NIO是如何基于Selector實(shí)現(xiàn)多路復(fù)用機(jī)制支持的海量連接。
NIO原理圖
多路復(fù)用機(jī)制是如何支持海量連接?
NIO的線程模型對(duì)Socket發(fā)起的連接不需要每個(gè)都創(chuàng)建一個(gè)線程,完全可以使用一個(gè)Selector來(lái)多路復(fù)用監(jiān)聽(tīng)N多個(gè)Channel是否有請(qǐng)求,該請(qǐng)求是對(duì)應(yīng)的連接請(qǐng)求,還是發(fā)送數(shù)據(jù)的請(qǐng)求。
這里面是基于操作系統(tǒng)底層的Select通知機(jī)制的,一個(gè)Selector不斷的輪詢多個(gè)Channel,這樣避免了創(chuàng)建多個(gè)線程。
只有當(dāng)莫個(gè)Channel有對(duì)應(yīng)的請(qǐng)求的時(shí)候才會(huì)創(chuàng)建線程,可能說(shuō)1000個(gè)請(qǐng)求, 只有100個(gè)請(qǐng)求是有數(shù)據(jù)交互的。
這個(gè)時(shí)候可能server端就提供10個(gè)線程就能夠處理這些請(qǐng)求。這樣的話就可以避免了創(chuàng)建大量的線程。
NIO如何通過(guò)Buffer來(lái)緩沖數(shù)據(jù)的。
NIO中的Buffer是個(gè)什么東西 ?
學(xué)習(xí)NIO,首當(dāng)其沖就是要了解所謂的Buffer緩沖區(qū),這個(gè)東西是NIO里比較核心的一個(gè)部分。
一般來(lái)說(shuō),如果你要通過(guò)NIO寫(xiě)數(shù)據(jù)到文件或者網(wǎng)絡(luò),或者是從文件和網(wǎng)絡(luò)讀取數(shù)據(jù)出來(lái)此時(shí)就需要通過(guò)Buffer緩沖區(qū)來(lái)進(jìn)行。Buffer的使用一般有如下幾個(gè)步驟:
寫(xiě)入數(shù)據(jù)到Buffer,調(diào)用flip()方法,從Buffer中讀取數(shù)據(jù),調(diào)用clear()方法或者compact()方法。
Buffer中對(duì)應(yīng)的Position, Mark, Capacity,Limit都啥?
capacity:緩沖區(qū)容量的大小,就是里面包含的數(shù)據(jù)大小。
limit:對(duì)buffer緩沖區(qū)使用的一個(gè)限制,從這個(gè)index開(kāi)始就不能讀取數(shù)據(jù)了。
position:代表著數(shù)組中可以開(kāi)始讀寫(xiě)的index, 不能大于limit。
mark:是類(lèi)似路標(biāo)的東西,在某個(gè)position的時(shí)候,設(shè)置一下mark,此時(shí)就可以設(shè)置一個(gè)標(biāo)記
后續(xù)調(diào)用reset()方法可以把position復(fù)位到當(dāng)時(shí)設(shè)置的那個(gè)mark上。去把position或limit調(diào)整為小于mark的值時(shí),就丟棄這個(gè)mark
如果使用的是Direct模式創(chuàng)建的Buffer的話,就會(huì)減少中間緩沖直接使用DirectorBuffer來(lái)進(jìn)行數(shù)據(jù)的存儲(chǔ)。
如何通過(guò)Channel和FileChannel讀取Buffer數(shù)據(jù)寫(xiě)入磁盤(pán)的
NIO中,Channel是什么?
Channel是NIO中的數(shù)據(jù)通道,類(lèi)似流,但是又有些不同。
Channel既可從中讀取數(shù)據(jù),又可以從寫(xiě)數(shù)據(jù)到通道中,但是流的讀寫(xiě)通常是單向的。
Channel可以異步的讀寫(xiě)。Channel中的數(shù)據(jù)總是要先讀到一個(gè)Buffer中,或者從緩沖區(qū)中將數(shù)據(jù)寫(xiě)到通道中。
FileChannel的作用是什么?
Buffer有不同的類(lèi)型,同樣Channel也有好幾個(gè)類(lèi)型。
- FileChannel
- DatagramChannel
- SocketChannel
- ServerSocketChannel
這些通道涵蓋了UDP 和 TCP 網(wǎng)絡(luò)IO,以及文件IO。而FileChannel就是文件IO對(duì)應(yīng)的管道, 在讀取文件的時(shí)候會(huì)用到這個(gè)管道。
public class FileChannelDemo1 {public static void main(String[] args) throws Exception {// 構(gòu)造一個(gè)傳統(tǒng)的文件輸出流FileOutputStream out = new FileOutputStream("/Users/tatatsuryou/Desktop/hello.txt");// 通過(guò)文件輸出流獲取到對(duì)應(yīng)的FileChannel,以NIO的方式來(lái)寫(xiě)文件FileChannel channel = out.getChannel();// 將數(shù)據(jù)寫(xiě)入到Buffer中ByteBuffer buffer = ByteBuffer.wrap(new String("Hello World").getBytes());// 通過(guò)FileChannel管道將Buffer中的數(shù)據(jù)寫(xiě)到輸出流中去,持久化到磁盤(pán)中去channel.write(buffer);channel.close();out.close();}
}
面試題總結(jié)
BIO,NIO,AIO 有什么區(qū)別?
簡(jiǎn)答
- BIO:Block IO 同步阻塞式 IO,就是我們平常使用的傳統(tǒng) IO,它的特點(diǎn)是模式簡(jiǎn)單使用方便,并發(fā)處理能力低。
- NIO:Non IO 同步非阻塞 IO,是傳統(tǒng) IO 的升級(jí),客戶端和服務(wù)器端通過(guò) Channel(通道)通訊,實(shí)現(xiàn)了多路復(fù)用。
- AIO:Asynchronous IO 是 NIO 的升級(jí),也叫 NIO2,實(shí)現(xiàn)了異步非堵塞 IO ,異步 IO 的操作基于事件和回調(diào)機(jī)制。
詳解
- BIO (Blocking I/O): 同步阻塞I/O模式,數(shù)據(jù)的讀取寫(xiě)入必須阻塞在一個(gè)線程內(nèi)等待其完成。在活動(dòng)連接數(shù)不是特別高(小于單機(jī)1000)的情況下,這種模型是比較不錯(cuò)的,可以讓每一個(gè)連接專(zhuān)注于自己的 I/O 并且編程模型簡(jiǎn)單,也不用過(guò)多考慮系統(tǒng)的過(guò)載、限流等問(wèn)題。線程池本身就是一個(gè)天然的漏斗,可以緩沖一些系統(tǒng)處理不了的連接或請(qǐng)求。但是,當(dāng)面對(duì)十萬(wàn)甚至百萬(wàn)級(jí)連接的時(shí)候,傳統(tǒng)的 BIO 模型是無(wú)能為力的。因此,我們需要一種更高效的 I/O 處理模型來(lái)應(yīng)對(duì)更高的并發(fā)量。
- NIO (New I/O): NIO是一種同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,對(duì)應(yīng) java.nio 包,提供了 Channel , Selector,Buffer等抽象。NIO中的N可以理解為Non-blocking,不單純是New。它支持面向緩沖的,基于通道的I/O操作方法。 NIO提供了與傳統(tǒng)BIO模型中的 Socket 和 ServerSocket 相對(duì)應(yīng)的 SocketChannel 和 ServerSocketChannel 兩種不同的套接字通道實(shí)現(xiàn),兩種通道都支持阻塞和非阻塞兩種模式。阻塞模式使用就像傳統(tǒng)中的支持一樣,比較簡(jiǎn)單,但是性能和可靠性都不好;非阻塞模式正好與之相反。對(duì)于低負(fù)載、低并發(fā)的應(yīng)用程序,可以使用同步阻塞I/O來(lái)提升開(kāi)發(fā)速率和更好的維護(hù)性;對(duì)于高負(fù)載、高并發(fā)的(網(wǎng)絡(luò))應(yīng)用,應(yīng)使用 NIO 的非阻塞模式來(lái)開(kāi)發(fā)
- AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改進(jìn)版 NIO 2,它是異步非阻塞的IO模型。異步 IO 是基于事件和回調(diào)機(jī)制實(shí)現(xiàn)的,也就是應(yīng)用操作之后會(huì)直接返回,不會(huì)堵塞在那里,當(dāng)后臺(tái)處理完成,操作系統(tǒng)會(huì)通知相應(yīng)的線程進(jìn)行后續(xù)的操作。AIO 是異步IO的縮寫(xiě),雖然 NIO 在網(wǎng)絡(luò)操作中,提供了非阻塞的方法,但是 NIO 的 IO 行為還是同步的。對(duì)于 NIO 來(lái)說(shuō),我們的業(yè)務(wù)線程是在 IO 操作準(zhǔn)備好時(shí),得到通知,接著就由這個(gè)線程自行進(jìn)行 IO 操作,IO操作本身是同步的。查閱網(wǎng)上相關(guān)資料,我發(fā)現(xiàn)就目前來(lái)說(shuō) AIO 的應(yīng)用還不是很廣泛,Netty 之前也嘗試使用過(guò) AIO,不過(guò)又放棄了。