NIO與IO
1、阻塞IO實(shí)例:
public void serve(int portNumber) throws IOException {
//創(chuàng)建一個新的 ServerSocket,用以監(jiān)聽指定端口上的連接請求
ServerSocket serverSocket = new ServerSocket(portNumber);
//對accept()方法的調(diào)用將被阻塞,直到一個連接建立
Socket clientSocket = serverSocket.accept();
//這些流對象都派生于該套接字的流對象,創(chuàng)建一個緩沖區(qū)存儲輸入流
BufferedReader in = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out =
new PrintWriter(clientSocket.getOutputStream(), true);
String request, response;
//處理循環(huán)開始
while ((request = in.readLine()) != null) {
if ("Done".equals(request)) {
break;
}
//請求被傳遞給服務(wù)器的處理方法
response = processRequest(request);
//服務(wù)器的響應(yīng)被發(fā)送給了客戶端
out.println(response);
//繼續(xù)執(zhí)行處理循環(huán)
}
}
private String processRequest(String request){
return "Processed";
}
上面的實(shí)例只能同時處理一個連接,要管理多個并發(fā)客戶端,需要為每個新的客戶端socket創(chuàng)建一個新的Thread,如下圖:

這種方案的影響:
①在任何時候都可能有大量的線程處于休眠狀態(tài),只是等待輸入或者輸出數(shù)據(jù)就緒,這可能算是一種資源浪費(fèi)。
②需要為每個線程的調(diào)用棧都分配內(nèi)存,其默認(rèn)值大小區(qū)間為64 KB到1 MB,具體取決于操作系統(tǒng)。
③即使Java虛擬機(jī)(JVM)在物理上可以支持非常大數(shù)量的線程,但是遠(yuǎn)在到達(dá)該極限之前,上下文切換所帶來的開銷就會帶來麻煩,例如,在達(dá)到10 000個連接的時候。
雖然這種并發(fā)方案對于支撐中小數(shù)量的客戶端來說還算可以接受,但是為了支撐100 000或者更多的并發(fā)連接所需要的資源使得它很不理想
2、NIO
非阻塞設(shè)計(jì),其實(shí)際上消除了IO的那些弊端。
實(shí)例:
public void serve(int port) throws IOException {
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
ServerSocket ss = serverChannel.socket();
InetSocketAddress address = new InetSocketAddress(port);
//將服務(wù)器綁定到選定的端口
ss.bind(address);
//打開Selector來處理 Channel
Selector selector = Selector.open();
//將ServerSocket注冊到Selector以接受連接
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes());
for (;;){
try {
//等待需要處理的新事件;阻塞將一直持續(xù)到下一個傳入事件
selector.select();
} catch (IOException ex) {
ex.printStackTrace();
//handle exception
break;
}
//獲取所有接收事件的SelectionKey實(shí)例
Set<SelectionKey> readyKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = readyKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
try {
//檢查事件是否是一個新的已經(jīng)就緒可以被接受的連接
if (key.isAcceptable()) {
ServerSocketChannel server =
(ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
//接受客戶端,并將它注冊到選擇器
client.register(selector, SelectionKey.OP_WRITE |
SelectionKey.OP_READ, msg.duplicate());
System.out.println(
"Accepted connection from " + client);
}
//檢查套接字是否已經(jīng)準(zhǔn)備好寫數(shù)據(jù)
if (key.isWritable()) {
SocketChannel client =
(SocketChannel) key.channel();
ByteBuffer buffer =
(ByteBuffer) key.attachment();
while (buffer.hasRemaining()) {
//將數(shù)據(jù)寫到已連接的客戶端
if (client.write(buffer) == 0) {
break;
}
}
//關(guān)閉連接
client.close();
}
} catch (IOException ex) {
key.cancel();
try {
key.channel().close();
} catch (IOException cex) {
// ignore on close
}
}
}
}
}

圖1-2 使用Selector的非阻塞I/O
class java.nio.channels.Selector是Java的非阻塞I/O實(shí)現(xiàn)的關(guān)鍵。它使用了事件通知API以確定在一組非阻塞套接字中有哪些已經(jīng)就緒能夠進(jìn)行I/O相關(guān)的操作。因?yàn)榭梢栽谌魏蔚臅r間檢查任意的讀操作或者寫操作的完成狀態(tài),所以如圖1-2所示,一個單一的線程便可以處理多個并發(fā)的連接。
總體來看,與阻塞I/O模型相比,這種模型提供了更好的資源管理:
使用較少的線程便可以處理許多連接,因此也減少了內(nèi)存管理和上下文切換所帶來開銷;
當(dāng)沒有I/O操作需要處理的時候,線程也可以被用于其他任務(wù)。
盡管已經(jīng)有許多直接使用Java NIO API的應(yīng)用程序被構(gòu)建了,但是要做到如此正確和安全并不容易。特別是,在高負(fù)載下可靠和高效地處理和調(diào)度I/O操作是一項(xiàng)繁瑣而且容易出錯的任務(wù)
參考:《Netty in Action》