姓名:周小蓬 16019110037
轉(zhuǎn)載自:http://blog.csdn.net/u010154380/article/details/78697367
[嵌牛導讀]
也許很多朋友在學習NIO的時候都會感覺有點吃力砰苍,對里面的很多概念都感覺不是那么明朗芦拿。在進入Java NIO編程之前,我們今天先來討論一些比較基礎的知識:I/O模型办绝。下面本文先從同步和異步的概念 說起惫企,然后接著闡述了阻塞和非阻塞的區(qū)別议薪,接著介紹了阻塞IO和非阻塞IO的區(qū)別,然后介紹了同步IO和異步IO的區(qū)別县匠,接下來介紹了5種IO模型风科,最后介紹了兩種和高性能IO設計相關(guān)的設計模式(Reactor和Proactor)。
[嵌牛鼻子]
java
[嵌牛提問]
什么是i/o模型
[嵌牛正文]
以下是本文的目錄大綱:
一.什么是同步乞旦?什么是異步贼穆?
二.什么是阻塞?什么是非阻塞兰粉?
三.什么是阻塞IO故痊?什么是非阻塞IO?
四.什么是同步IO玖姑?什么是異步IO崖蜜?
五.五種IO模型
六.兩種高性能IO設計模式
若有不正之處隧熙,請多多諒解并歡迎批評指正苟鸯。
請尊重作者勞動成果,轉(zhuǎn)載請標明原文鏈接:
http://www.cnblogs.com/dolphin0520/p/3916526.html
一.什么是同步辟宗?什么是異步舔琅?
同步和異步的概念出來已經(jīng)很久了等恐,網(wǎng)上有關(guān)同步和異步的說法也有很多。以下是我個人的理解:
同步就是:如果有多個任務或者事件要發(fā)生,這些任務或者事件必須逐個地進行课蔬,一個事件或者任務的執(zhí)行會導致整個流程的暫時等待囱稽,這些事件沒有辦法并發(fā)地執(zhí)行;
異步就是:如果有多個任務或者事件發(fā)生二跋,這些事件可以并發(fā)地執(zhí)行战惊,一個事件或者任務的執(zhí)行不會導致整個流程的暫時等待。
這就是同步和異步扎即。舉個簡單的例子吞获,假如有一個任務包括兩個子任務A和B,對于同步來說谚鄙,當A在執(zhí)行的過程中各拷,B只有等待,直至A執(zhí)行完畢闷营,B才能執(zhí)行烤黍;而對于異步就是A和B可以并發(fā)地執(zhí)行,B不必等待A執(zhí)行完畢之后再執(zhí)行傻盟,這樣就不會由于A的執(zhí)行導致整個任務的暫時等待速蕊。
如果還不理解,可以先看下面這2段代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
voidfun1() {
}
voidfun2() {
}
voidfunction(){
fun1();
fun2()
.....
.....
}
這段代碼就是典型的同步娘赴,在方法function中规哲,fun1在執(zhí)行的過程中會導致后續(xù)的fun2無法執(zhí)行,fun2必須等待fun1執(zhí)行完畢才可以執(zhí)行筝闹。
接著看下面這段代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
voidfun1() {
}
voidfun2() {
}
voidfunction(){
newThread(){
publicvoidrun() {
fun1();
}
}.start();
newThread(){
publicvoidrun() {
fun2();
}
}.start();
.....
.....
}
這段代碼是一種典型的異步,fun1的執(zhí)行不會影響到fun2的執(zhí)行腥光,并且fun1和fun2的執(zhí)行不會導致其后續(xù)的執(zhí)行過程處于暫時的等待关顷。
事實上,同步和異步是一個非常廣的概念武福,它們的重點在于多個任務和事件發(fā)生時议双,一個事件的發(fā)生或執(zhí)行是否會導致整個流程的暫時等待。我覺得可以將同步和異步與Java中的synchronized關(guān)鍵字聯(lián)系起來進行類比捉片。當多個線程同時訪問一個變量時平痰,每個線程訪問該變量就是一個事件伍纫,對于同步來說宗雇,就是這些線程必須逐個地來訪問該變量,一個線程在訪問該變量的過程中莹规,其他線程必須等待赔蒲;而對于異步來說,就是多個線程不必逐個地訪問該變量,可以同時進行訪問舞虱。
因此欢际,個人覺得同步和異步可以表現(xiàn)在很多方面,但是記住其關(guān)鍵在于多個任務和事件發(fā)生時矾兜,一個事件的發(fā)生或執(zhí)行是否會導致整個流程的暫時等待损趋。一般來說,可以通過多線程的方式來實現(xiàn)異步椅寺,但是千萬記住不要將多線程和異步畫上等號浑槽,異步只是宏觀上的一個模式,采用多線程來實現(xiàn)異步只是一種手段配并,并且通過多進程的方式也可以實現(xiàn)異步括荡。
二.什么是阻塞?什么是非阻塞溉旋?
在前面介紹了同步和異步的區(qū)別畸冲,這一節(jié)來看一下阻塞和非阻塞的區(qū)別。
阻塞就是:當某個事件或者任務在執(zhí)行過程中观腊,它發(fā)出一個請求操作邑闲,但是由于該請求操作需要的條件不滿足,那么就會一直在那等待梧油,直至條件滿足苫耸;
非阻塞就是:當某個事件或者任務在執(zhí)行過程中,它發(fā)出一個請求操作儡陨,如果該請求操作需要的條件不滿足褪子,會立即返回一個標志信息告知條件不滿足,不會一直在那等待骗村。
這就是阻塞和非阻塞的區(qū)別嫌褪。也就是說阻塞和非阻塞的區(qū)別關(guān)鍵在于當發(fā)出請求一個操作時,如果條件不滿足胚股,是會一直等待還是返回一個標志信息笼痛。
舉個簡單的例子:
假如我要讀取一個文件中的內(nèi)容,如果此時文件中沒有內(nèi)容可讀琅拌,對于同步來說就是會一直在那等待缨伊,直至文件中有內(nèi)容可讀;而對于非阻塞來說进宝,就會直接返回一個標志信息告知文件中暫時無內(nèi)容可讀刻坊。
在網(wǎng)上有一些朋友將同步和異步分別與阻塞和非阻塞畫上等號,事實上党晋,它們是兩組完全不同的概念紧唱。注意活尊,理解這兩組概念的區(qū)別對于后面IO模型的理解非常重要。
同步和異步著重點在于多個任務的執(zhí)行過程中漏益,一個任務的執(zhí)行是否會導致整個流程的暫時等待蛹锰;
而阻塞和非阻塞著重點在于發(fā)出一個請求操作時,如果進行操作的條件不滿足是否會返會一個標志信息告知條件不滿足绰疤。
理解阻塞和非阻塞可以同線程阻塞類比地理解铜犬,當一個線程進行一個請求操作時,如果條件不滿足轻庆,則會被阻塞癣猾,即在那等待條件滿足。
三.什么是阻塞IO余爆?什么是非阻塞IO纷宇?
在了解阻塞IO和非阻塞IO之前,先看下一個具體的IO操作過程是怎么進行的蛾方。
通常來說像捶,IO操作包括:對硬盤的讀寫、對socket的讀寫以及外設的讀寫桩砰。
當用戶線程發(fā)起一個IO請求操作(本文以讀請求操作為例)拓春,內(nèi)核會去查看要讀取的數(shù)據(jù)是否就緒,對于阻塞IO來說亚隅,如果數(shù)據(jù)沒有就緒硼莽,則會一直在那等待,直到數(shù)據(jù)就緒煮纵;對于非阻塞IO來說懂鸵,如果數(shù)據(jù)沒有就緒,則會返回一個標志信息告知用戶線程當前要讀的數(shù)據(jù)沒有就緒行疏。當數(shù)據(jù)就緒之后匆光,便將數(shù)據(jù)拷貝到用戶線程,這樣才完成了一個完整的IO讀請求操作隘擎,也就是說一個完整的IO讀請求操作包括兩個階段:
1)查看數(shù)據(jù)是否就緒殴穴;
2)進行數(shù)據(jù)拷貝(內(nèi)核將數(shù)據(jù)拷貝到用戶線程)凉夯。
那么阻塞(blocking IO)和非阻塞(non-blocking IO)的區(qū)別就在于第一個階段货葬,如果數(shù)據(jù)沒有就緒,在查看數(shù)據(jù)是否就緒的過程中是一直等待劲够,還是直接返回一個標志信息震桶。
Java中傳統(tǒng)的IO都是阻塞IO,比如通過socket來讀數(shù)據(jù)征绎,調(diào)用read()方法之后蹲姐,如果數(shù)據(jù)沒有就緒磨取,當前線程就會一直阻塞在read方法調(diào)用那里,直到有數(shù)據(jù)才返回柴墩;而如果是非阻塞IO的話忙厌,當數(shù)據(jù)沒有就緒,read()方法應該返回一個標志信息江咳,告知當前線程數(shù)據(jù)沒有就緒逢净,而不是一直在那里等待。
四.什么是同步IO歼指?什么是異步IO爹土?
我們先來看一下同步IO和異步IO的定義,在《Unix網(wǎng)絡編程》一書中對同步IO和異步IO的定義是這樣的:
A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes.
An asynchronous I/O operation does not cause the requesting process to be blocked.
從字面的意思可以看出:同步IO即 如果一個線程請求進行IO操作踩身,在IO操作完成之前胀茵,該線程會被阻塞;
而異步IO為 如果一個線程請求進行IO操作挟阻,IO操作不會導致請求線程被阻塞琼娘。
事實上,同步IO和異步IO模型是針對用戶線程和內(nèi)核的交互來說的:
對于同步IO:當用戶發(fā)出IO請求操作之后赁濒,如果數(shù)據(jù)沒有就緒轨奄,需要通過用戶線程或者內(nèi)核不斷地去輪詢數(shù)據(jù)是否就緒,當數(shù)據(jù)就緒時拒炎,再將數(shù)據(jù)從內(nèi)核拷貝到用戶線程挪拟;
而異步IO:只有IO請求操作的發(fā)出是由用戶線程來進行的,IO操作的兩個階段都是由內(nèi)核自動完成击你,然后發(fā)送通知告知用戶線程IO操作已經(jīng)完成玉组。也就是說在異步IO中,不會對用戶線程產(chǎn)生任何阻塞丁侄。
這是同步IO和異步IO關(guān)鍵區(qū)別所在惯雳,同步IO和異步IO的關(guān)鍵區(qū)別反映在數(shù)據(jù)拷貝階段是由用戶線程完成還是內(nèi)核完成。所以說異步IO必須要有操作系統(tǒng)的底層支持鸿摇。
注意同步IO和異步IO與阻塞IO和非阻塞IO是不同的兩組概念石景。
阻塞IO和非阻塞IO是反映在當用戶請求IO操作時,如果數(shù)據(jù)沒有就緒拙吉,是用戶線程一直等待數(shù)據(jù)就緒潮孽,還是會收到一個標志信息這一點上面的。也就是說筷黔,阻塞IO和非阻塞IO是反映在IO操作的第一個階段往史,在查看數(shù)據(jù)是否就緒時是如何處理的。
五.五種IO模型
在《Unix網(wǎng)絡編程》一書中提到了五種IO模型佛舱,分別是:阻塞IO椎例、非阻塞IO挨决、多路復用IO、信號驅(qū)動IO以及異步IO订歪。
下面就分別來介紹一下這5種IO模型的異同脖祈。
1.阻塞IO模型
最傳統(tǒng)的一種IO模型,即在讀寫數(shù)據(jù)過程中會發(fā)生阻塞現(xiàn)象刷晋。
當用戶線程發(fā)出IO請求之后撒犀,內(nèi)核會去查看數(shù)據(jù)是否就緒,如果沒有就緒就會等待數(shù)據(jù)就緒掏秩,而用戶線程就會處于阻塞狀態(tài)或舞,用戶線程交出CPU。當數(shù)據(jù)就緒之后蒙幻,內(nèi)核會將數(shù)據(jù)拷貝到用戶線程映凳,并返回結(jié)果給用戶線程,用戶線程才解除block狀態(tài)邮破。
典型的阻塞IO模型的例子為:
1data = socket.read();
如果數(shù)據(jù)沒有就緒诈豌,就會一直阻塞在read方法。
2.非阻塞IO模型
當用戶線程發(fā)起一個read操作后抒和,并不需要等待矫渔,而是馬上就得到了一個結(jié)果。如果結(jié)果是一個error時摧莽,它就知道數(shù)據(jù)還沒有準備好庙洼,于是它可以再次發(fā)送read操作。一旦內(nèi)核中的數(shù)據(jù)準備好了镊辕,并且又再次收到了用戶線程的請求油够,那么它馬上就將數(shù)據(jù)拷貝到了用戶線程,然后返回征懈。
所以事實上石咬,在非阻塞IO模型中,用戶線程需要不斷地詢問內(nèi)核數(shù)據(jù)是否就緒卖哎,也就說非阻塞IO不會交出CPU鬼悠,而會一直占用CPU。
典型的非阻塞IO模型一般如下:
1
2
3
4
5
6
7
while(true){
data = socket.read();
if(data!= error){
處理數(shù)據(jù)
break;
}
}
但是對于非阻塞IO就有一個非常嚴重的問題亏娜,在while循環(huán)中需要不斷地去詢問內(nèi)核數(shù)據(jù)是否就緒焕窝,這樣會導致CPU占用率非常高,因此一般情況下很少使用while循環(huán)這種方式來讀取數(shù)據(jù)照藻。
3.多路復用IO模型
多路復用IO模型是目前使用得比較多的模型袜啃。Java NIO實際上就是多路復用IO汗侵。
在多路復用IO模型中幸缕,會有一個線程不斷去輪詢多個socket的狀態(tài)群发,只有當socket真正有讀寫事件時,才真正調(diào)用實際的IO讀寫操作发乔。因為在多路復用IO模型中熟妓,只需要使用一個線程就可以管理多個socket,系統(tǒng)不需要建立新的進程或者線程栏尚,也不必維護這些線程和進程起愈,并且只有在真正有socket讀寫事件進行時,才會使用IO資源译仗,所以它大大減少了資源占用抬虽。
在Java NIO中,是通過selector.select()去查詢每個通道是否有到達事件纵菌,如果沒有事件阐污,則一直阻塞在那里,因此這種方式會導致用戶線程的阻塞咱圆。
也許有朋友會說笛辟,我可以采用 多線程+ 阻塞IO 達到類似的效果,但是由于在多線程 + 阻塞IO 中序苏,每個socket對應一個線程手幢,這樣會造成很大的資源占用,并且尤其是對于長連接來說忱详,線程的資源一直不會釋放围来,如果后面陸續(xù)有很多連接的話,就會造成性能上的瓶頸匈睁。
而多路復用IO模式管钳,通過一個線程就可以管理多個socket,只有當socket真正有讀寫事件發(fā)生才會占用資源來進行實際的讀寫操作软舌。因此才漆,多路復用IO比較適合連接數(shù)比較多的情況。
另外多路復用IO為何比非阻塞IO模型的效率高是因為在非阻塞IO中佛点,不斷地詢問socket狀態(tài)時通過用戶線程去進行的醇滥,而在多路復用IO中,輪詢每個socket狀態(tài)是內(nèi)核在進行的超营,這個效率要比用戶線程要高的多鸳玩。
不過要注意的是,多路復用IO模型是通過輪詢的方式來檢測是否有事件到達演闭,并且對到達的事件逐一進行響應不跟。因此對于多路復用IO模型來說,一旦事件響應體很大米碰,那么就會導致后續(xù)的事件遲遲得不到處理窝革,并且會影響新的事件輪詢购城。
4.信號驅(qū)動IO模型
在信號驅(qū)動IO模型中,當用戶線程發(fā)起一個IO請求操作虐译,會給對應的socket注冊一個信號函數(shù)瘪板,然后用戶線程會繼續(xù)執(zhí)行,當內(nèi)核數(shù)據(jù)就緒時會發(fā)送一個信號給用戶線程漆诽,用戶線程接收到信號之后侮攀,便在信號函數(shù)中調(diào)用IO讀寫操作來進行實際的IO請求操作。
5.異步IO模型
異步IO模型才是最理想的IO模型厢拭,在異步IO模型中兰英,當用戶線程發(fā)起read操作之后,立刻就可以開始去做其它的事供鸠。而另一方面箭昵,從內(nèi)核的角度,當它受到一個asynchronous read之后回季,它會立刻返回家制,說明read請求已經(jīng)成功發(fā)起了,因此不會對用戶線程產(chǎn)生任何block泡一。然后颤殴,內(nèi)核會等待數(shù)據(jù)準備完成,然后將數(shù)據(jù)拷貝到用戶線程鼻忠,當這一切都完成之后涵但,內(nèi)核會給用戶線程發(fā)送一個信號,告訴它read操作完成了帖蔓。也就說用戶線程完全不需要實際的整個IO操作是如何進行的矮瘟,只需要先發(fā)起一個請求,當接收內(nèi)核返回的成功信號時表示IO操作已經(jīng)完成塑娇,可以直接去使用數(shù)據(jù)了澈侠。
也就說在異步IO模型中,IO操作的兩個階段都不會阻塞用戶線程埋酬,這兩個階段都是由內(nèi)核自動完成哨啃,然后發(fā)送一個信號告知用戶線程操作已完成。用戶線程中不需要再次調(diào)用IO函數(shù)進行具體的讀寫写妥。這點是和信號驅(qū)動模型有所不同的拳球,在信號驅(qū)動模型中,當用戶線程接收到信號表示數(shù)據(jù)已經(jīng)就緒珍特,然后需要用戶線程調(diào)用IO函數(shù)進行實際的讀寫操作祝峻;而在異步IO模型中,收到信號表示IO操作已經(jīng)完成,不需要再在用戶線程中調(diào)用iO函數(shù)進行實際的讀寫操作莱找。
注意酬姆,異步IO是需要操作系統(tǒng)的底層支持,在Java 7中宋距,提供了Asynchronous IO。
前面四種IO模型實際上都屬于同步IO症脂,只有最后一種是真正的異步IO谚赎,因為無論是多路復用IO還是信號驅(qū)動模型,IO操作的第2個階段都會引起用戶線程阻塞诱篷,也就是內(nèi)核進行數(shù)據(jù)拷貝的過程都會讓用戶線程阻塞壶唤。
六.兩種高性能IO設計模式
在傳統(tǒng)的網(wǎng)絡服務設計模式中,有兩種比較經(jīng)典的模式:
一種是 多線程棕所,一種是線程池闸盔。
對于多線程模式,也就說來了client琳省,服務器就會新建一個線程來處理該client的讀寫事件迎吵,如下圖所示:
這種模式雖然處理起來簡單方便,但是由于服務器為每個client的連接都采用一個線程去處理针贬,使得資源占用非常大击费。因此,當連接數(shù)量達到上限時桦他,再有用戶請求連接蔫巩,直接會導致資源瓶頸,嚴重的可能會直接導致服務器崩潰快压。
因此圆仔,為了解決這種一個線程對應一個客戶端模式帶來的問題,提出了采用線程池的方式蔫劣,也就說創(chuàng)建一個固定大小的線程池坪郭,來一個客戶端,就從線程池取一個空閑線程來處理脉幢,當客戶端處理完讀寫操作之后截粗,就交出對線程的占用。因此這樣就避免為每一個客戶端都要創(chuàng)建線程帶來的資源浪費鸵隧,使得線程可以重用绸罗。
但是線程池也有它的弊端,如果連接大多是長連接豆瘫,因此可能會導致在一段時間內(nèi)珊蟀,線程池中的線程都被占用,那么當再有用戶請求連接時,由于沒有可用的空閑線程來處理育灸,就會導致客戶端連接失敗腻窒,從而影響用戶體驗。因此磅崭,線程池比較適合大量的短連接應用儿子。
因此便出現(xiàn)了下面的兩種高性能IO設計模式:Reactor和Proactor。
在Reactor模式中砸喻,會先對每個client注冊感興趣的事件柔逼,然后有一個線程專門去輪詢每個client是否有事件發(fā)生,當有事件發(fā)生時割岛,便順序處理每個事件愉适,當所有事件處理完之后,便再轉(zhuǎn)去繼續(xù)輪詢癣漆,如下圖所示:
從這里可以看出维咸,上面的五種IO模型中的多路復用IO就是采用Reactor模式。注意惠爽,上面的圖中展示的 是順序處理每個事件癌蓖,當然為了提高事件處理速度,可以通過多線程或者線程池的方式來處理事件婚肆。
在Proactor模式中费坊,當檢測到有事件發(fā)生時,會新起一個異步操作旬痹,然后交由內(nèi)核線程去處理附井,當內(nèi)核線程完成IO操作之后,發(fā)送一個通知告知操作已完成两残,可以得知永毅,異步IO模型采用的就是Proactor模式。
參考資料:
《Unix網(wǎng)絡編程》
http://blog.csdn.net/goldensuny/article/details/30717107
http://my.oschina.net/XYleung/blog/295122
http://xmuzyq.iteye.com/blog/783218
http://www.cnblogs.com/ccdev/p/3542669.html
http://alicsd.iteye.com/blog/868702
http://www.smithfox.com/?e=191
http://www.cnblogs.com/Anker/p/3254269.html
http://blog.csdn.net/hguisu/article/details/7453390
http://www.cnblogs.com/dawen/archive/2011/05/18/2050358.html