面試知識點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()應該是阻塞的。
另外一個值得注意的問題是:由於只使用了一個執行緒(多個執行緒也如此)處理使用者請求,所以要避免執行緒被阻塞,解決方法是事件的處理者必須要即刻返回,不能陷入迴圈中,否則會影響其他使用者的請求速度。