概念
一个SelectableChannel
对象的多路复用器,即Selector
可以使一个单独的线程管理多个Channel
,Selector
是非阻塞IO的核心(多路复用)
引入
- 传统的IO 流都是阻塞式的。也就是说,当一个线程调用read() 或write() 时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务。因此,在完成网络通信进行IO 操作时,由于线程会阻塞,所以服务器端必须为每个客户端都提供一个独立的线程进行处理, 当服务器端需要处理大量客户端时,性能急剧下降。
- NIO 是非阻塞模式的。当线程从某通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。线程通常将非阻塞IO 的空闲时间用于在其他通道上执行IO 操作,所以单独的线程可以管理多个输入和输出通道。因此,NIO 可以让服务器端使用一个或有限几个线程来同时处理连接到服务器端的所有客户端
如何创建
- 一个
selector
可以通过执行这个类的open
方法创建,该方法将使用系统的默认选择器提供程序来创建新的选择器。
Selector.open()
- 通过一个自定义的
selector
提供者的openSelector
方法
private final SelectorProvider provider;
provider.openSelector();
- 一个
selector
选择器将保持打开状态,知道调用close
方法使其关闭
selector.close();
Selection key
一个可选择的channel
注册一个selector
后可以由一个SelectionKey
表示、一个selector
会维护三组selection key
-
key set
里包含的key
表示当前注册到``selector的channel
。可以通过调用keys
方法返回这个集合() -
selected-key set
通过方法selectedKeys
返回关注事件的集合 -
cancelled-key set
没有方法直接访问,是key set
的一个子集,已经被取消但是频道还没有被注销
当selector
刚被创建的时候,这三种集合都是空的。
通过channel
的register
方法,把一个接受的事件的key
的channel
注册到selector
// 把我们的serverSocketChannel注册到 selector,关心事件 OP_ACCEPT
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
通过Selection的操作事件,可以把key加入到selected-key set
里
public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;
可以调用key的remove方法,或者iterator的remove方法,将key从selected-key set
里移除
// 手动从集合中溢出当前的selectionKey,防止重复操作
keyIterator.remove();