面試知識點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()應該是阻塞的。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章