Java NIO的一個很是重要的特色就是,引入了非阻塞的I/O。其中Selector類是非阻塞I/O的核心類。Selector是一個可以檢查多個Channel,並發現哪些Channel已經準備好了數據傳送的NIO組件。這樣能夠在一個線程去管理多個Channel,例如多個網絡鏈接。Selector與 Windows 消息循環相似,它從不一樣客戶機捕獲各類事件並將它們分派到相應的事件處理程序。html
一個常見的網絡 IO 通信流程以下 :java
圖 2 網絡 IO 通信流程服務器
在通信過程當中若鏈接還沒到來,那麼accept會阻塞,程序運行到這裏不得不掛起,CPU 轉而執行其餘線程。一樣,read 會同樣也會阻塞。網絡
阻塞式網絡 IO 的特色:多線程處理多個鏈接,例如10000個鏈接就須要10000個線程(消耗資源),而且阻塞的結果就是會帶來大量的線程頻繁地進行上下文切換(消耗時間)。多線程
非阻塞式IO的出現的目的就是爲了解決這個瓶頸。而非阻塞式IO是怎麼實現的呢?非阻塞IO處理鏈接的線程數和鏈接數沒有聯繫,也就是說處理10000個鏈接非阻塞IO不須要10000個線程,你能夠用1000個也能夠用2000個線程來處理。由於非阻塞IO處理鏈接是異步的。當某個鏈接發送請求到服務器,服務器把這個鏈接請求看成一個請求「事件」,並把這個"事件"分配給相應的函數處理。咱們能夠把這個處理函數放到線程中去執行,執行完就把線程歸還。這樣一個線程就能夠異步的處理多個事件。而阻塞式IO的線程的大部分時間都浪費在等待請求上了。併發
圖 3 Java NIO: A Thread uses a Selector to handle 3 Channel's異步
下面是一個非阻塞式網絡通訊的例子,分爲服務器端和客戶端程序。socket
NonBlockingServer.java:ide
import java.net.InetSocketAddress;函數
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Set;
public class NonBlockingServer {
public static void main(String[] args) throws Exception {
// Define a set of ports.
int ports[] = { 8000, 8001, 8002, 8005 };
// Creating a Selector.
Selector selector = Selector.open();
// Open some ServerSocketChannels and register in the selector.
for (int i : ports) {
ServerSocketChannel initServerSocketChannel = ServerSocketChannel
.open();
// The Channel must be in non-blocking mode to be used with a
// Selector.
initServerSocketChannel.configureBlocking(false);
ServerSocket initServerSocket = initServerSocketChannel.socket();
initServerSocket.bind(new InetSocketAddress("127.0.0.1", i));
// Registering Channels with the Selector.
initServerSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server is listenning on port " + i);
}
// Once you have register one or more channels with a Selector you can
// call one of the select()
// methods. These methods return the channels that are "ready" for the
// events you are interested in
// (connect, accept, read or write). In other words, if you are
// interested in channels that are ready for
// reading, you will receive the channels that are ready for reading
// from the select() methods.
// Here are the select() methods:
// int select()
// int select(long timeout)
// int selectNow()
// select() blocks until at least one channel is ready for the events
// you registered for.
// select(long timeout) does the same as select() except it blocks for a
// maximum of timeout milliseconds (the parameter).
// selectNow() doesn't block at all. It returns immediately with
// whatever channels are ready.
// The int returned by the select() methods tells how many channels are
// ready. That is, how many channels that became ready since last time
// you called select(). If you call select() and it returns 1 because
// one channel has become ready, and you call select() one more time,
// and one more channel has become ready, it will return 1 again. If you
// have done nothing with the first channel that was ready, you now have
// 2 ready channels, but only one channel had become ready between each
// select() call.
while (selector.select() > 0) {
Set<SelectionKey> selectedKeys = selector.selectedKeys();
for (SelectionKey key : selectedKeys) {
if (key.isAcceptable()) {
// Accessing the channel from the SelectionKey.
ServerSocketChannel server = (ServerSocketChannel) key
.channel();
SocketChannel client = server.accept();
System.out.println("Accepted");
client.configureBlocking(false);
ByteBuffer outBuf = ByteBuffer.allocate(1024);
outBuf.put(("Current Time is " + new Date()).getBytes());
outBuf.flip();
client.write(outBuf);
client.close();
}
}
selectedKeys.clear();
}
}
}
Client.java:
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.Set;
public class Client {
public static void main(String[] args) throws Exception {
Selector selector = Selector.open();
SocketChannel initClientSocketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8001));
// You must set the Channel to non-blocking.
initClientSocketChannel.configureBlocking(false);
initClientSocketChannel.register(selector, SelectionKey.OP_READ);
while(selector.select()>0){
Set<SelectionKey> selectedKeys = selector.selectedKeys();
for(SelectionKey key : selectedKeys) {
if(key.isReadable()) {
SocketChannel clientSocketChannel = (SocketChannel) key.channel();
ByteBuffer inBuf = ByteBuffer.allocate(1024);
clientSocketChannel.read(inBuf);
inBuf.flip();
Charset charset = Charset.forName( "utf-8" );
CharsetDecoder decoder = charset.newDecoder();
CharBuffer charBuffer = decoder.decode(inBuf);
System.out.println(charBuffer);
}
}
}
}
}
事實上,客觀地說,NIO的性能跟傳統線程池性能的比較孰優孰劣有待考證,筆者能力有限,暫時沒有寫出能夠供測試的模型出來,若是讀者有興趣能夠參考下圖寫一個服務器程序出來。
圖 4 NIO服務器模型
參考資料:
http://g.oswego.edu/dl/cpjslides/nio.pdf