Java IO:BIO和NIO區別及各自應用場景

 

引言

BIO和NIO是兩種不一樣的網絡通訊模型,現現在NIO已經大量應用在Jetty、ZooKeeper、Netty等開源框架中。react

 

一個面向流、一個面向緩衝區後端

一個是阻塞式的、一個非阻塞安全

一個沒有io多路複用器、一個有服務器

下面經過一個例子解釋二者區別:網絡

假設當前服務端程序須要同時從與多個客戶端創建的鏈接讀取數據。多線程

 

使用BIO

若是採用阻塞式IO,單線程狀況下,處理者線程可能阻塞在其中一個套接字的read上,致使另外一個套接字即便準備好了數據也沒法處理,這個時候解決的方法就是針對每個套接字,都新建一個線程處理其數據讀取。併發

因此說,在BIO工做模式下,服務端程序要想同時處理多個套接字的數據讀取,在等待接收鏈接請求的主線程以外,還要爲每個創建好的鏈接分配一個新的線程進行處理。框架

 

使用NIO

輪詢方式函數

若是將套接字讀操做換成非阻塞的,那麼只須要一個線程就能夠同時處理套接字,每次檢查一個套接字,有數據則讀取,沒有則檢查下一個,由於是非阻塞的,因此執行read操做時若沒有數據準備好則當即返回,不會發生阻塞。性能

 

I/O多路複用

這種輪詢的方式缺點是浪費CPU資源,大部分時間可能都是無數據可讀的, 沒必要仍不間斷的反覆執行read操做,I/O多路複用(IOmultiplexing)是一種更好的方法,調用select函數時,其內部會維護一張監聽 的套接字的列表,其會一直阻塞直到其中某一個套接字有數據準備好才返回,並告訴是哪一個套接字可讀,這時再調用該套接字的read函數效率更高。

 

因此基本能夠認爲 「NIO = I/O多路複用 + 非阻塞式I/O」,大部分狀況下是單線程,但也有超過一個線程實現NIO的狀況

 

 

NIO三種模型

上面所講到的只須要一個線程就能夠同時處理多個套接字,這只是其中的一種單線程模型,是一種較爲極端的狀況,NIO主要包含三種線程模型:

1) Reactor單線程模型

2) Reactor多線程模型

3)主從Reactor多線程模型

 

Reactor單線程模型:

單個線程完成全部事情包括接收客戶端的TCP鏈接請求,讀取和寫入套接字數據等。

對於一些小容量應用場景,可使用單線程模型。可是對於高負載、大併發的應用卻不合適,主要緣由以下:

1) 一個NIO線程同時處理成百上千的鏈路,性能上沒法支撐,即使NIO線程的CPU負荷達到100%,也沒法知足海量消息的編碼、解碼、讀取和發送;

2) 當NIO線程負載太重以後,處理速度將變慢,這會致使大量客戶端鏈接超時,超時以後每每會進行重發,這更加劇了NIO線程的負載,最終會致使大量消息積壓和處理超時,NIO線程會成爲系統的性能瓶頸;

3) 可靠性問題:一旦NIO線程意外跑飛,或者進入死循環,會致使整個系統通訊模塊不可用,不能接收和處理外部消息,形成節點故障。

爲了解決這些問題,演進出了Reactor多線程模型。

 

Reactor多線程模型:

Rector多線程模型與單線程模型最大的區別就是有一組NIO線程處理真實的IO操做。

Reactor多線程模型的特色:

1) 有專門一個NIO線程-Acceptor線程用於監聽服務端,接收客戶端的TCP鏈接請求;

2) 網絡IO操做-讀、寫等由一個NIO線程池負責,線程池能夠採用標準的JDK線程池實現,它包含一個任務隊列和N個可用的線程,由這些NIO線程負責消息的讀取、解碼、編碼和發送;

3) 1個NIO線程能夠同時處理N條鏈路,可是1個鏈路只對應1個NIO線程,防止發生併發操做問題。

在絕大多數場景下,Reactor多線程模型均可以知足性能需求;可是,在極特殊應用場景中,一個NIO線程負責監聽和處理全部的客戶端鏈接可能會存在性能問題。例如百萬客戶端併發鏈接,或者服務端須要對客戶端的握手消息進行安全認證,認證自己很是損耗性能。在這類場景下,單獨一個Acceptor線程可能會存在性能不足問題,爲了解決性能問題,產生了第三種Reactor線程模型-主從Reactor多線程模型。

即從單線程中由一個線程即監聽鏈接事件、讀寫事件、由完成數據讀寫,拆分爲由一個線程專門監聽各類事件,再由專門的線程池負責處理真正的IO數據讀寫。

 

主從Reactor多線程模型

主從Reactor線程模型與Reactor多線程模型的最大區別就是有一組NIO線程處理鏈接、讀寫事件。

主 從Reactor線程模型的特色是:服務端用於接收客戶端鏈接的再也不是個1個單獨的NIO線程,而是一個獨立的NIO線程池。Acceptor接收到客戶 端TCP鏈接請求處理完成後(可能包含接入認證等),將新建立的SocketChannel註冊到IO線程池(sub reactor線程池)的某個IO線程上,由它負責SocketChannel的讀寫和編解碼工做。Acceptor線程池僅僅只用於客戶端的登錄、握手 和安全認證,一旦鏈路創建成功,就將鏈路註冊到後端subReactor線程池的IO線程上,由IO線程負責後續的IO操做。

即從多線程模型中由一個線程來監聽鏈接事件和數據讀寫事件,拆分爲一個線程監聽鏈接事件,線程池的多個線程監聽已經創建鏈接的套接字的數據讀寫事件,另外和多線程模型同樣有專門的線程池處理真正的IO操做。

 

各自適用場景

NIO適用場景

服務器須要支持超大量長時間鏈接。好比10000個鏈接以上,而且每一個客戶端並不會頻繁地發送太多數據。例如總公司的一箇中心服務器須要收集全國便利店各個收銀機的交易信息,只須要少許線程按需處理維護的大量長期鏈接。

Jetty、Mina、Netty、ZooKeeper等都是基於NIO方式實現。

相關文章
相關標籤/搜索