Java NIO淺析I/O模型

      網(wǎng)友投稿 850 2025-03-31

      也許很多朋友在學(xué)習(xí)NIO的時候都會感覺有點吃力,對里面的很多概念都感覺不是那么明朗。在進(jìn)入Java NIO編程之前,我們今天先來討論一些比較基礎(chǔ)的知識:I/O模型。下面本文先從同步和異步的概念 說起,然后接著闡述了阻塞和非阻塞的區(qū)別,接著介紹了阻塞IO和非阻塞IO的區(qū)別,然后介紹了同步IO和異步IO的區(qū)別,接下來介紹了5種IO模型,最后介紹了兩種和高性能IO設(shè)計相關(guān)的設(shè)計模式(Reactor和Proactor)。


      以下是本文的目錄大綱:

      一.什么是同步?什么是異步?

      二.什么是阻塞?什么是非阻塞?

      三.什么是阻塞IO?什么是非阻塞IO?

      四.什么是同步IO?什么是異步IO?

      五.五種IO模型

      六.兩種高性能IO設(shè)計模式

      一.什么是同步?什么是異步?

      同步和異步的概念出來已經(jīng)很久了,網(wǎng)上有關(guān)同步和異步的說法也有很多。以下是我個人的理解:

      同步就是:如果有多個任務(wù)或者事件要發(fā)生,這些任務(wù)或者事件必須逐個地進(jìn)行,一個事件或者任務(wù)的執(zhí)行會導(dǎo)致整個流程的暫時等待,這些事件沒有辦法并發(fā)地執(zhí)行;

      異步就是:如果有多個任務(wù)或者事件發(fā)生,這些事件可以并發(fā)地執(zhí)行,一個事件或者任務(wù)的執(zhí)行不會導(dǎo)致整個流程的暫時等待。

      這就是同步和異步。舉個簡單的例子,假如有一個任務(wù)包括兩個子任務(wù)A和B,對于同步來說,當(dāng)A在執(zhí)行的過程中,B只有等待,直至A執(zhí)行完畢,B才能執(zhí)行;而對于異步就是A和B可以并發(fā)地執(zhí)行,B不必等待A執(zhí)行完畢之后再執(zhí)行,這樣就不會由于A的執(zhí)行導(dǎo)致整個任務(wù)的暫時等待。

      如果還不理解,可以先看下面這2段代碼:

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      void fun1() {

      }

      void fun2() {

      }

      void function(){

      fun1();

      fun2()

      .....

      .....

      }

      這段代碼就是典型的同步,在方法function中,fun1在執(zhí)行的過程中會導(dǎo)致后續(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

      void fun1() {

      }

      void fun2() {

      }

      void function(){

      new Thread(){

      public void run() {

      fun1();

      }

      }.start();

      new Thread(){

      public void run() {

      fun2();

      }

      }.start();

      .....

      .....

      }

      這段代碼是一種典型的異步,fun1的執(zhí)行不會影響到fun2的執(zhí)行,并且fun1和fun2的執(zhí)行不會導(dǎo)致其后續(xù)的執(zhí)行過程處于暫時的等待。

      事實上,同步和異步是一個非常廣的概念,它們的重點在于多個任務(wù)和事件發(fā)生時,一個事件的發(fā)生或執(zhí)行是否會導(dǎo)致整個流程的暫時等待。我覺得可以將同步和異步與Java中的synchronized關(guān)鍵字聯(lián)系起來進(jìn)行類比。當(dāng)多個線程同時訪問一個變量時,每個線程訪問該變量就是一個事件,對于同步來說,就是這些線程必須逐個地來訪問該變量,一個線程在訪問該變量的過程中,其他線程必須等待;而對于異步來說,就是多個線程不必逐個地訪問該變量,可以同時進(jìn)行訪問。

      因此,個人覺得同步和異步可以表現(xiàn)在很多方面,但是記住其關(guān)鍵在于多個任務(wù)和事件發(fā)生時,一個事件的發(fā)生或執(zhí)行是否會導(dǎo)致整個流程的暫時等待。一般來說,可以通過多線程的方式來實現(xiàn)異步,但是千萬記住不要將多線程和異步畫上等號,異步只是宏觀上的一個模式,采用多線程來實現(xiàn)異步只是一種手段,并且通過多進(jìn)程的方式也可以實現(xiàn)異步。

      二.什么是阻塞?什么是非阻塞?

      在前面介紹了同步和異步的區(qū)別,這一節(jié)來看一下阻塞和非阻塞的區(qū)別。

      阻塞就是:當(dāng)某個事件或者任務(wù)在執(zhí)行過程中,它發(fā)出一個請求操作,但是由于該請求操作需要的條件不滿足,那么就會一直在那等待,直至條件滿足;

      非阻塞就是:當(dāng)某個事件或者任務(wù)在執(zhí)行過程中,它發(fā)出一個請求操作,如果該請求操作需要的條件不滿足,會立即返回一個標(biāo)志信息告知條件不滿足,不會一直在那等待。

      這就是阻塞和非阻塞的區(qū)別。也就是說阻塞和非阻塞的區(qū)別關(guān)鍵在于當(dāng)發(fā)出請求一個操作時,如果條件不滿足,是會一直等待還是返回一個標(biāo)志信息。

      舉個簡單的例子:

      假如我要讀取一個文件中的內(nèi)容,如果此時文件中沒有內(nèi)容可讀,對于同步來說就是會一直在那等待,直至文件中有內(nèi)容可讀;而對于非阻塞來說,就會直接返回一個標(biāo)志信息告知文件中暫時無內(nèi)容可讀。

      在網(wǎng)上有一些朋友將同步和異步分別與阻塞和非阻塞畫上等號,事實上,它們是兩組完全不同的概念。注意,理解這兩組概念的區(qū)別對于后面IO模型的理解非常重要。

      同步和異步著重點在于多個任務(wù)的執(zhí)行過程中,一個任務(wù)的執(zhí)行是否會導(dǎo)致整個流程的暫時等待;

      而阻塞和非阻塞著重點在于發(fā)出一個請求操作時,如果進(jìn)行操作的條件不滿足是否會返會一個標(biāo)志信息告知條件不滿足。

      理解阻塞和非阻塞可以同線程阻塞類比地理解,當(dāng)一個線程進(jìn)行一個請求操作時,如果條件不滿足,則會被阻塞,即在那等待條件滿足。

      三.什么是阻塞IO?什么是非阻塞IO?

      在了解阻塞IO和非阻塞IO之前,先看下一個具體的IO操作過程是怎么進(jìn)行的。

      通常來說,IO操作包括:對硬盤的讀寫、對socket的讀寫以及外設(shè)的讀寫。

      當(dāng)用戶線程發(fā)起一個IO請求操作(本文以讀請求操作為例),內(nèi)核會去查看要讀取的數(shù)據(jù)是否就緒,對于阻塞IO來說,如果數(shù)據(jù)沒有就緒,則會一直在那等待,直到數(shù)據(jù)就緒;對于非阻塞IO來說,如果數(shù)據(jù)沒有就緒,則會返回一個標(biāo)志信息告知用戶線程當(dāng)前要讀的數(shù)據(jù)沒有就緒。當(dāng)數(shù)據(jù)就緒之后,便將數(shù)據(jù)拷貝到用戶線程,這樣才完成了一個完整的IO讀請求操作,也就是說一個完整的IO讀請求操作包括兩個階段:

      1)查看數(shù)據(jù)是否就緒;

      2)進(jìn)行數(shù)據(jù)拷貝(內(nèi)核將數(shù)據(jù)拷貝到用戶線程)。

      那么阻塞(blocking IO)和非阻塞(non-blocking IO)的區(qū)別就在于第一個階段,如果數(shù)據(jù)沒有就緒,在查看數(shù)據(jù)是否就緒的過程中是一直等待,還是直接返回一個標(biāo)志信息。

      Java中傳統(tǒng)的IO都是阻塞IO,比如通過socket來讀數(shù)據(jù),調(diào)用read()方法之后,如果數(shù)據(jù)沒有就緒,當(dāng)前線程就會一直阻塞在read方法調(diào)用那里,直到有數(shù)據(jù)才返回;而如果是非阻塞IO的話,當(dāng)數(shù)據(jù)沒有就緒,read()方法應(yīng)該返回一個標(biāo)志信息,告知當(dāng)前線程數(shù)據(jù)沒有就緒,而不是一直在那里等待。

      Java NIO:淺析I/O模型

      四.什么是同步IO?什么是異步IO?

      我們先來看一下同步IO和異步IO的定義,在《Unix網(wǎng)絡(luò)編程》一書中對同步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即 如果一個線程請求進(jìn)行IO操作,在IO操作完成之前,該線程會被阻塞;

      而異步IO為 如果一個線程請求進(jìn)行IO操作,IO操作不會導(dǎo)致請求線程被阻塞。

      事實上,同步IO和異步IO模型是針對用戶線程和內(nèi)核的交互來說的:

      對于同步IO:當(dāng)用戶發(fā)出IO請求操作之后,如果數(shù)據(jù)沒有就緒,需要通過用戶線程或者內(nèi)核不斷地去輪詢數(shù)據(jù)是否就緒,當(dāng)數(shù)據(jù)就緒時,再將數(shù)據(jù)從內(nèi)核拷貝到用戶線程;

      而異步IO:只有IO請求操作的發(fā)出是由用戶線程來進(jìn)行的,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是反映在當(dāng)用戶請求IO操作時,如果數(shù)據(jù)沒有就緒,是用戶線程一直等待數(shù)據(jù)就緒,還是會收到一個標(biāo)志信息這一點上面的。也就是說,阻塞IO和非阻塞IO是反映在IO操作的第一個階段,在查看數(shù)據(jù)是否就緒時是如何處理的。

      五.五種IO模型

      在《Unix網(wǎng)絡(luò)編程》一書中提到了五種IO模型,分別是:阻塞IO、非阻塞IO、多路復(fù)用IO、信號驅(qū)動IO以及異步IO。

      下面就分別來介紹一下這5種IO模型的異同。

      1.阻塞IO模型

      最傳統(tǒng)的一種IO模型,即在讀寫數(shù)據(jù)過程中會發(fā)生阻塞現(xiàn)象。

      當(dāng)用戶線程發(fā)出IO請求之后,內(nèi)核會去查看數(shù)據(jù)是否就緒,如果沒有就緒就會等待數(shù)據(jù)就緒,而用戶線程就會處于阻塞狀態(tài),用戶線程交出CPU。當(dāng)數(shù)據(jù)就緒之后,內(nèi)核會將數(shù)據(jù)拷貝到用戶線程,并返回結(jié)果給用戶線程,用戶線程才解除block狀態(tài)。

      典型的阻塞IO模型的例子為:

      1

      data = socket.read();

      如果數(shù)據(jù)沒有就緒,就會一直阻塞在read方法。

      2.非阻塞IO模型

      當(dāng)用戶線程發(fā)起一個read操作后,并不需要等待,而是馬上就得到了一個結(jié)果。如果結(jié)果是一個error時,它就知道數(shù)據(jù)還沒有準(zhǔn)備好,于是它可以再次發(fā)送read操作。一旦內(nèi)核中的數(shù)據(jù)準(zhǔn)備好了,并且又再次收到了用戶線程的請求,那么它馬上就將數(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就有一個非常嚴(yán)重的問題,在while循環(huán)中需要不斷地去詢問內(nèi)核數(shù)據(jù)是否就緒,這樣會導(dǎo)致CPU占用率非常高,因此一般情況下很少使用while循環(huán)這種方式來讀取數(shù)據(jù)。

      3.多路復(fù)用IO模型

      多路復(fù)用IO模型是目前使用得比較多的模型。Java NIO實際上就是多路復(fù)用IO。

      在多路復(fù)用IO模型中,會有一個線程不斷去輪詢多個socket的狀態(tài),只有當(dāng)socket真正有讀寫事件時,才真正調(diào)用實際的IO讀寫操作。因為在多路復(fù)用IO模型中,只需要使用一個線程就可以管理多個socket,系統(tǒng)不需要建立新的進(jìn)程或者線程,也不必維護(hù)這些線程和進(jìn)程,并且只有在真正有socket讀寫事件進(jìn)行時,才會使用IO資源,所以它大大減少了資源占用。

      在Java NIO中,是通過selector.select()去查詢每個通道是否有到達(dá)事件,如果沒有事件,則一直阻塞在那里,因此這種方式會導(dǎo)致用戶線程的阻塞。

      也許有朋友會說,我可以采用 多線程+ 阻塞IO 達(dá)到類似的效果,但是由于在多線程 + 阻塞IO 中,每個socket對應(yīng)一個線程,這樣會造成很大的資源占用,并且尤其是對于長連接來說,線程的資源一直不會釋放,如果后面陸續(xù)有很多連接的話,就會造成性能上的瓶頸。

      而多路復(fù)用IO模式,通過一個線程就可以管理多個socket,只有當(dāng)socket真正有讀寫事件發(fā)生才會占用資源來進(jìn)行實際的讀寫操作。因此,多路復(fù)用IO比較適合連接數(shù)比較多的情況。

      另外多路復(fù)用IO為何比非阻塞IO模型的效率高是因為在非阻塞IO中,不斷地詢問socket狀態(tài)時通過用戶線程去進(jìn)行的,而在多路復(fù)用IO中,輪詢每個socket狀態(tài)是內(nèi)核在進(jìn)行的,這個效率要比用戶線程要高的多。

      不過要注意的是,多路復(fù)用IO模型是通過輪詢的方式來檢測是否有事件到達(dá),并且對到達(dá)的事件逐一進(jìn)行響應(yīng)。因此對于多路復(fù)用IO模型來說,一旦事件響應(yīng)體很大,那么就會導(dǎo)致后續(xù)的事件遲遲得不到處理,并且會影響新的事件輪詢。

      4.信號驅(qū)動IO模型

      在信號驅(qū)動IO模型中,當(dāng)用戶線程發(fā)起一個IO請求操作,會給對應(yīng)的socket注冊一個信號函數(shù),然后用戶線程會繼續(xù)執(zhí)行,當(dāng)內(nèi)核數(shù)據(jù)就緒時會發(fā)送一個信號給用戶線程,用戶線程接收到信號之后,便在信號函數(shù)中調(diào)用IO讀寫操作來進(jìn)行實際的IO請求操作。

      5.異步IO模型

      異步IO模型才是最理想的IO模型,在異步IO模型中,當(dāng)用戶線程發(fā)起read操作之后,立刻就可以開始去做其它的事。而另一方面,從內(nèi)核的角度,當(dāng)它受到一個asynchronous read之后,它會立刻返回,說明read請求已經(jīng)成功發(fā)起了,因此不會對用戶線程產(chǎn)生任何block。然后,內(nèi)核會等待數(shù)據(jù)準(zhǔn)備完成,然后將數(shù)據(jù)拷貝到用戶線程,當(dāng)這一切都完成之后,內(nèi)核會給用戶線程發(fā)送一個信號,告訴它read操作完成了。也就說用戶線程完全不需要實際的整個IO操作是如何進(jìn)行的,只需要先發(fā)起一個請求,當(dāng)接收內(nèi)核返回的成功信號時表示IO操作已經(jīng)完成,可以直接去使用數(shù)據(jù)了。

      也就說在異步IO模型中,IO操作的兩個階段都不會阻塞用戶線程,這兩個階段都是由內(nèi)核自動完成,然后發(fā)送一個信號告知用戶線程操作已完成。用戶線程中不需要再次調(diào)用IO函數(shù)進(jìn)行具體的讀寫。這點是和信號驅(qū)動模型有所不同的,在信號驅(qū)動模型中,當(dāng)用戶線程接收到信號表示數(shù)據(jù)已經(jīng)就緒,然后需要用戶線程調(diào)用IO函數(shù)進(jìn)行實際的讀寫操作;而在異步IO模型中,收到信號表示IO操作已經(jīng)完成,不需要再在用戶線程中調(diào)用iO函數(shù)進(jìn)行實際的讀寫操作。

      注意,異步IO是需要操作系統(tǒng)的底層支持,在Java 7中,提供了Asynchronous IO。

      前面四種IO模型實際上都屬于同步IO,只有最后一種是真正的異步IO,因為無論是多路復(fù)用IO還是信號驅(qū)動模型,IO操作的第2個階段都會引起用戶線程阻塞,也就是內(nèi)核進(jìn)行數(shù)據(jù)拷貝的過程都會讓用戶線程阻塞。

      六.兩種高性能IO設(shè)計模式

      在傳統(tǒng)的網(wǎng)絡(luò)服務(wù)設(shè)計模式中,有兩種比較經(jīng)典的模式:

      一種是 多線程,一種是線程池。

      對于多線程模式,也就說來了client,服務(wù)器就會新建一個線程來處理該client的讀寫事件,如下圖所示:

      這種模式雖然處理起來簡單方便,但是由于服務(wù)器為每個client的連接都采用一個線程去處理,使得資源占用非常大。因此,當(dāng)連接數(shù)量達(dá)到上限時,再有用戶請求連接,直接會導(dǎo)致資源瓶頸,嚴(yán)重的可能會直接導(dǎo)致服務(wù)器崩潰。

      因此,為了解決這種一個線程對應(yīng)一個客戶端模式帶來的問題,提出了采用線程池的方式,也就說創(chuàng)建一個固定大小的線程池,來一個客戶端,就從線程池取一個空閑線程來處理,當(dāng)客戶端處理完讀寫操作之后,就交出對線程的占用。因此這樣就避免為每一個客戶端都要創(chuàng)建線程帶來的資源浪費,使得線程可以重用。

      但是線程池也有它的弊端,如果連接大多是長連接,因此可能會導(dǎo)致在一段時間內(nèi),線程池中的線程都被占用,那么當(dāng)再有用戶請求連接時,由于沒有可用的空閑線程來處理,就會導(dǎo)致客戶端連接失敗,從而影響用戶體驗。因此,線程池比較適合大量的短連接應(yīng)用。

      因此便出現(xiàn)了下面的兩種高性能IO設(shè)計模式:Reactor和Proactor。

      在Reactor模式中,會先對每個client注冊感興趣的事件,然后有一個線程專門去輪詢每個client是否有事件發(fā)生,當(dāng)有事件發(fā)生時,便順序處理每個事件,當(dāng)所有事件處理完之后,便再轉(zhuǎn)去繼續(xù)輪詢,如下圖所示:

      從這里可以看出,上面的五種IO模型中的多路復(fù)用IO就是采用Reactor模式。注意,上面的圖中展示的 是順序處理每個事件,當(dāng)然為了提高事件處理速度,可以通過多線程或者線程池的方式來處理事件。

      在Proactor模式中,當(dāng)檢測到有事件發(fā)生時,會新起一個異步操作,然后交由內(nèi)核線程去處理,當(dāng)內(nèi)核線程完成IO操作之后,發(fā)送一個通知告知操作已完成,可以得知,異步IO模型采用的就是Proactor模式。

      Java 任務(wù)調(diào)度

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:怎么實現(xiàn)IF函數(shù)計數(shù)
      下一篇:excel按類型填充單元格序號方法介紹
      相關(guān)文章
      亚洲精品第一国产综合精品99| 亚洲精品美女久久久久久久| 精品国产_亚洲人成在线| 亚洲av无码一区二区三区天堂古代| 久久久亚洲欧洲日产国码农村| 亚洲一级片内射网站在线观看| 午夜在线亚洲男人午在线| 久久精品国产亚洲AV天海翼| 亚洲老熟女五十路老熟女bbw| 在线综合亚洲中文精品| 亚洲成a人片在线不卡| 亚洲欧洲日韩极速播放| 亚洲一区二区三区乱码在线欧洲| 亚洲av成人一区二区三区| 亚洲AV无码乱码在线观看代蜜桃 | 亚洲sm另类一区二区三区| 亚洲色欲啪啪久久WWW综合网| 99久久国产亚洲综合精品| 亚洲一线产区二线产区区| 亚洲人成网站免费播放| 亚洲精品无码久久| 久久久久亚洲国产AV麻豆| 亚洲M码 欧洲S码SSS222| 亚洲精品无码久久久久AV麻豆| 亚洲乱码中文字幕手机在线| 一本色道久久综合亚洲精品高清| 中文字幕亚洲第一| 亚洲成AV人片在线观看无 | 亚洲成年网站在线观看| 亚洲国产日韩a在线播放| 自拍偷自拍亚洲精品偷一| 亚洲av区一区二区三| 久久夜色精品国产亚洲av| 亚洲精品成人片在线播放| 亚洲精品高清久久| 亚洲一区二区久久| 亚洲码欧美码一区二区三区| 亚洲高清免费视频| 亚洲成av人在线视| 亚洲欧洲久久精品| 亚洲日韩一区二区三区|