1. 程式人生 > >Java NIO | 概念

Java NIO | 概念

部落格引用處(以下內容在原有部落格基礎上進行補充或更改,謝謝這些大牛的部落格指導):
深入理解Java NIO

NIO是什麼?

NIO 是一種同步非阻塞的 IO 模型。同步是指執行緒不斷輪詢 IO 事件是否就緒,非阻塞是指執行緒在等待 IO 的時候,可以同時做其他任務。同步的核心就是 Selector,Selector 代替了執行緒本身輪詢 IO 事件,避免了阻塞同時減少了不必要的執行緒消耗;非阻塞的核心就是通道和緩衝區,當 IO 事件就緒時,可以通過寫道緩衝區,保證 IO 的成功,而無需執行緒阻塞式地等待。

非阻塞核心之: Buffer

為什麼說NIO是基於緩衝區的IO方式呢?因為,當一個連結建立完成後,IO的資料未必會馬上到達,為了當資料到達時能夠正確完成IO操作,在BIO(阻塞IO)中,等待IO的執行緒必須被阻塞,以全天候地執行IO操作。為了解決這種IO方式低效的問題,引入了緩衝區的概念,當資料到達時,可以預先被寫入緩衝區,再由緩衝區交給執行緒,因此執行緒無需阻塞地等待IO。

非阻塞核心之: 通道

當執行:SocketChannel.write(Buffer),便將一個 buffer 寫到了一個通道中。如果說緩衝區還好理解,通道相對來說就更加抽象。網上部落格難免有寫不嚴謹的地方,容易使初學者感到難以理解。

引用 Java NIO 中權威的說法:通道是 I/O 傳輸發生時通過的入口,而緩衝區是這些數 據傳輸的來源或目標。對於離開緩衝區的傳輸,您想傳遞出去的資料被置於一個緩衝區,被傳送到通道。對於傳回緩衝區的傳輸,一個通道將資料放置在您所提供的緩衝區中。

例如 有一個伺服器通道 ServerSocketChannel serverChannel,一個客戶端通道 SocketChannel clientChannel;伺服器緩衝區:serverBuffer,客戶端緩衝區:clientBuffer。

當伺服器想向客戶端傳送資料時,需要呼叫:clientChannel.write(serverBuffer)。當客戶端要讀時,呼叫 clientChannel.read(clientBuffer)
當客戶端想向伺服器傳送資料時,需要呼叫:serverChannel.write(clientBuffer)。當伺服器要讀時,呼叫 serverChannel.read(serverBuffer)

這樣,通道和緩衝區的關係似乎更好理解了。在實踐中,未必會出現這種雙向連線的蠢事(然而這確實存在的,後面的內容還會涉及),但是可以理解為在NIO中:如果想將Data發到目標端,則需要將儲存該Data的Buffer,寫入到目標端的Channel中,然後再從Channel中讀取資料到目標端的Buffer中。

同步的核心:Selector

通道和緩衝區的機制,使得執行緒無需阻塞地等待IO事件的就緒,但是總是要有人來監管這些IO事件。這個工作就交給了selector來完成,這就是所謂的同步。

Selector允許單執行緒處理多個 Channel。如果你的應用打開了多個連線(通道),但每個連線的流量都很低,使用Selector就會很方便。

要使用Selector,得向Selector註冊Channel,然後呼叫它的select()方法。這個方法會一直阻塞到某個註冊的通道有事件就緒,這就是所說的輪詢。一旦這個方法返回,執行緒就可以處理這些事件。

Selector中註冊的感興趣事件有:

  • OP_ACCEPT
  • OP_CONNECT
  • OP_READ
  • OP_WRITE

優化:

一種優化方式是:將Selector進一步分解為Reactor,將不同的感興趣事件分開,每一個Reactor只負責一種感興趣的事件。這樣做的好處是:1、分離阻塞級別,減少了輪詢的時間;2、執行緒無需遍歷set以找到自己感興趣的事件,因為得到的set中僅包含自己感興趣的事件。
在這裡插入圖片描述

NIO和epoll:

epoll是Linux核心的IO模型。我想一定有人想問,AIO聽起來比NIO更加高大上,為什麼不使用AIO?AIO其實也有應用,但是有一個問題就是,Linux是不支援AIO的,因此基於AIO的程式執行在Linux上的效率相比NIO反而更低。而Linux是最主要的伺服器OS,因此相比AIO,目前NIO的應用更加廣泛。

說到這裡,可能你已經明白了,epoll一定和NIO有著很深的因緣。沒錯,如果仔細研究epoll的技術內幕,你會發現它確實和NIO非常相似,都是基於“通道”和緩衝區的,也有selector,只是在epoll中,通道實際上是作業系統的“管道”。和NIO不同的是,NIO中,解放了執行緒,但是需要由selector阻塞式地輪詢IO事件的就緒;而epoll中,IO事件就緒後,會自動傳送訊息,通知selector:“我已經就緒了。”可以認為,Linux的epoll是一種效率更高的NIO。

nio的優點:

1、客戶端發起的連線操作connect是非同步的,可以通過在多路複用器selector上監聽connect事件等待後續結果,不需要像之前的客戶端那樣被同步阻塞;

2、SocketChannel的讀寫操作都是非同步的,如果沒有可讀寫的資料它不會同步等待,直接返回,這樣IO執行緒就可以處理其它的鏈路,不需要同步等待這個鏈路可用;

3、執行緒模型的優化,由於jdk的selector在linux等主流作業系統上通過epoll實現,它沒有連線控制代碼數的限制(只受限與作業系統的最大控制代碼數或者單個程序的控制代碼限制),這意味這一個selector可以連線成千上萬個客戶端連線,而效能不會隨著客戶端連線數的增長呈線性下降,因此,它適合做高效能、高負載的網路伺服器;