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