天天看點

面試知識點NIO-非阻塞I/O(轉)

原文:

NIO的使用http://hi.baidu.com/zbzb/blog/item/ba775eee89e2b2fbb3fb9515.html

使用Java NIO編寫高性能的伺服器http://www.javaeye.com/post/192013

一. 介紹NIO

NIO包(java.nio.*)引入了四個關鍵的抽象資料類型,它們共同解決傳統的I/O類中的一些問題。

1. Buffer:它是包含資料且用于讀寫的線形表結構。其中還提供了一個特殊類用于記憶體映射檔案的I/O操作。

2. Charset:它提供Unicode字元串影射到位元組序列以及逆影射的操作。

3. Channels:包含socket,file和pipe三種管道,它實際上是雙向交流的通道。

4. Selector:它将多元異步I/O操作集中到一個或多個線程中(它可以被看成是Unix中select()函數或Win32中WaitForSingleEvent()函數的面向對象版本)。

二. 回顧傳統

在介紹NIO之前,有必要了解傳統的I/O操作的方式。以網絡應用為例,傳統方式需要監聽一個ServerSocket,接受請求的

連接配接為其提供服務(服務通常包括了處理請求并發送響應)。

可以分析建立伺服器的每個具體步驟。

首先建立ServerSocket

ServerSocket server=new ServerSocket(10000);

然後接受新的連接配接請求

Socket newConnection=server.accept();

對于accept方法的調用将造成阻塞,直到ServerSocket接受到一個連接配接請求為止。一旦連接配接請求被接受,

伺服器可以讀客戶socket中的請求。

InputStream in = newConnection.getInputStream();

InputStreamReader reader = new InputStreamReader(in);

BufferedReader buffer = new BufferedReader(reader);

Request request = new Request();

while(!request.isComplete()) {

  String line = buffer.readLine();

  request.addLine(line);

}

這樣的操作有兩個問題,首先BufferedReader類的readLine()方法在其緩沖區未滿時會造成線程阻塞,隻有一定資料填滿了

緩沖區或者客戶關閉了套接字,方法才會傳回。其次,它回産生大量的垃圾,BufferedReader建立了緩沖區來從客戶套接字讀入資料,但是同樣建立了一些字元串存儲這些資料。雖然BufferedReader内部提供了StringBuffer處理這一問題,但是所有的String

很快變成了垃圾需要回收。同樣的問題在發送響應代碼中也存在

Response response = request.generateResponse();

OutputStream out = newConnection.getOutputStream();

InputStream in = response.getInputStream();

int ch;

while(-1 != (ch = in.read())) {

  out.write(ch);

}

newConnection.close();

類似的,讀寫操作被阻塞而且向流中一次寫入一個字元會造成效率低下,是以應該使用緩沖區,但是一旦使用緩沖,流又會産生更多的垃圾。

傳統的解決方法通常在Java中處理阻塞I/O要用到線程(大量的線程)。一般是實作一個線程池用來處理請求。線程使得伺服器可以處理多個連接配接,但是它們也同樣引發了許多問題。每個線程擁有自己的棧空間并且占用一些CPU時間,耗費很大,而且很多時間是浪費在阻塞的I/O操作上,沒有有效的利用CPU。

三.NIO的反應器模式

NIO伺服器最核心的一點就是反應器模式:當有感興趣的事件發生的,就通知對應的事件處理器去處理這個事件,如果沒有,則不處理。是以使用一個線程做輪詢就可以了。當然這裡這是個例子,如果要獲得更高性能,可以使用少量的線程,一個負責接收請求,其他的負責處理請求,特别是對于多CPU時效率會更高。

//注冊事件

client.register(selector, SelectionKey.OP_CONNECT);

channel.register(selector, SelectionKey.OP_READ);

//實作Selector監聽的永真循環,判斷事件

if (key.isConnectable()) {do your work}

if (key.isReadable()) {do your work}

關于使用NIO過程中出現的問題,最為普遍的就是為什麼沒有請求時CPU的占用率為100%?

出現這種問題的主要原因是注冊了不感興趣的事件,比如如果沒有資料要發到用戶端,而又注冊了寫事件(OP_WRITE),則在 Selector.select()上就會始終有事件出現,CPU就一直處理了,而此時select()應該是阻塞的。

另外一個值得注意的問題是:由于隻使用了一個線程(多個線程也如此)處理使用者請求,是以要避免線程被阻塞,解決方法是事件的處理者必須要即刻傳回,不能陷入循環中,否則會影響其他使用者的請求速度。