天天看點

java NIO BIO和AIO

借鑒于:https://zhuanlan.zhihu.com/p/23488863

NIO:NEW IO或not blocking IO,非阻塞的io模型,也是i/o多路複用的基礎;

傳統的io為bio(blocking i/o),阻塞io;使用bio的經典方式如下:

{

 ExecutorService executor = Excutors.newFixedThreadPollExecutor(100);//線程池

 ServerSocket serverSocket = new ServerSocket();

 serverSocket.bind(8088);

 while(!Thread.currentThread.isInturrupted()){//主線程死循環等待新連接配接到來

 Socket socket = serverSocket.accept();

 executor.submit(new ConnectIOnHandler(socket));//為新的連接配接建立新的線程

}

class ConnectIOnHandler extends Thread{

    private Socket socket;

    public ConnectIOnHandler(Socket socket){

       this.socket = socket;

    }

    public void run(){

      while(!Thread.currentThread.isInturrupted()&&!socket.isClosed()){死循環處理讀寫事件

          String someThing = socket.read()....//讀取資料

          if(someThing!=null){

             ......//處理資料

             socket.write()....//寫資料

          }

       }

    }

}

經典的每連接配接每線程的模型,之是以使用多線程,主要原因在于socket.accept()、socket.read()、socket.write()三個主要函數都是同步阻塞的,當一個連接配接在處理I/O的時候,系統是阻塞的,如果是單線程的話必然就挂死在那裡;但CPU是被釋放出來的,開啟多線程,就可以讓CPU去處理更多的事情。其實這也是所有使用多線程的本質:

1)利用多核。

2)當I/O阻塞系統,但CPU空閑的時候,可以利用多線程使用CPU資源。

現在的多線程一般都使用線程池,可以讓線程的建立和回收成本相對較低。在活動連接配接數不是特别高(小于單機1000)的情況下,這種模型是比較不錯的,可以讓每一個連接配接專注于自己的I/O并且程式設計模型簡單,也不用過多考慮系統的過載、限流等問題。線程池本身就是一個天然的漏鬥,可以緩沖一些系統處理不了的連接配接或請求。

不過,這個模型最本質的問題在于,嚴重依賴于線程。但線程是很"貴"的資源,主要表現在:

a線程的建立和銷毀成本很高,在Linux這樣的作業系統中,線程本質上就是一個程序。建立和銷毀都是重量級的系統函數。

b線程本身占用較大記憶體,像Java的線程棧,一般至少配置設定512K~1M的空間,如果系統中的線程數過千,恐怕整個JVM的記憶體都會被吃掉一半。

c線程的切換成本是很高的。作業系統發生線程切換的時候,需要保留線程的上下文,然後執行系統調用。如果線程數過高,可能執行線程切換的時間甚至會大于線程執行的時間,這時候帶來的表現往往是系統load偏高、CPU sy使用率特别高(超過20%以上),導緻系統幾乎陷入不可用的狀态。

d容易造成鋸齒狀的系統負載。因為系統負載是用活動線程數或CPU核心數,一旦線程數量高但外部網絡環境不是很穩定,就很容易造成大量請求的結果同時傳回,激活大量阻塞線程進而使系統負載壓力過大。

是以,當面對十萬甚至百萬級連接配接的時候,傳統的BIO模型是無能為力的。随着移動端應用的興起和各種網絡遊戲的盛行,百萬級長連接配接日趨普遍,此時,必然需要一種更高效的I/O處理模型。

是以出現了not blocking i/o用于解決上面的問題

Nio特點:socket主要的讀寫注冊和接收函數,在等待階段都是非阻塞的,真正的I/O操作同步阻塞的.

BIO\NIO\AIO的比較:

所有的系統I/O分為兩個部分:等待就緒和操作.如讀函數,分為等待系統可讀和真正的讀;但等待就緒不使用CPU,但真正的讀寫操作的是使用cpu的,當然操作執行期間也是一種阻塞.

以socket.read()為例:

對于BIO:socket,read()時,如果TCP RecvBuffer裡面沒有資料,函數會一直阻塞,直到收到資料,傳回讀到的資料.

NIO:如果TCP RecvBuffer裡面沒有資料,則直接傳回0,永遠不會阻塞;如果有資料,則直接把資料從網卡讀到記憶體,此時程序需要間斷查詢資料是否已經讀完..

AIO:不但等待就緒是非阻塞的,就連資料從網卡讀到記憶體的過程也是異步的,此時不再需要程序去查詢是否已經讀完,而是直接可以幹别的事,資料已經讀完時會主動告知程序

對NIO的使用:

NIO的讀寫函數可以立刻傳回,這就給了我們不開線程利用CPU的最好機會:如果一個連接配接不能讀寫(socket.read()傳回0或者socket.write()傳回0),我們可以把這件事記下來,記錄的方式通常是在Selector上注冊标記位,然後切換到其它就緒的連接配接(channel)繼續進行讀寫。

  interface ChannelHandler{

      void channelReadable(Channel channel);

      void channelWritable(Channel channel);

   }

   class Channel{

     Socket socket;

     Event event;//讀,寫或者連接配接

   }

   //IO線程主循環:

   class IoThread extends Thread{

   public void run(){

   Channel channel;

   while(channel=Selector.select()){//選擇就緒的事件和對應的連接配接

      if(channel.event==accept){

         registerNewChannelHandler(channel);//如果是新連接配接,則注冊一個新的讀寫處理器

      }

      if(channel.event==write){

         getChannelHandler(channel).channelWritable(channel);//如果可以寫,則執行寫事件

      }

      if(channel.event==read){

          getChannelHandler(channel).channelReadable(channel);//如果可以讀,則執行讀事件

      }

    }

   }

   Map<Channel,ChannelHandler> handlerMap;//所有channel的對應事件處理器

  }

這個程式很簡短,也是最簡單的Reactor模式:注冊所有感興趣的事件處理器,單線程輪詢選擇就緒事件,執行事件處理器。

借鑒于:https://zhuanlan.zhihu.com/p/23488863