【Java NIO 简例】Selector

本文深入探讨Java NIO Selector的工作原理,如何使用Selector检查多个Channel的读写状态,实现单线程处理多连接,降低线程切换成本。文章详细讲解了Selector的创建、Channel注册、事件监听与处理流程,以及如何通过Selector优化网络应用程序的性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

原文:《Java NIO Selector

Selector 可以检查多个 Channel 实例,发现那些已经就绪,可以读/写的 Channel。

通过这个机制,可以实现 单线程处理多个Channel,从而处理多个网络连接。

 

为什么要使用 Selector ?

对操作系统来说,线程之间的切换代价较高,而且每个线程都会占用一些内存资源。所以线程越少越好。

而利用Select可以实现只用一个线程处理多个 Channel。

现代操作系统和CPU在多任务处理方面越来越强,多线程开销也更小了。事实上,对于一个多核CPU,如果不采用多任务,可能就是在浪费CPU的算力。但这些属于另一个话题范畴。

 

 

创建 Selector

Java代码

 

  1. Selector selector = Selector.open();  

 

将 Channel 注册到 Selector

Java代码

 

  1. channel.configureBlocking(false);  

  2. SelectionKey key = channel.register(selector, SelectionKey.OP_READ);  

  • 此处的 Channel 必须设置为 非阻塞 模式。

    FileChannel 没有非阻塞模式,所以它不能与 Selector 共用;SocketChannel 有非阻塞模式,所以它可以。

  • SelectionKey.OP_READ 表示需要监听 Channel 的 Read 事件。

    即,当 Channel 可以被读取时,Selector 会将 OP_READ 加入相应 SelectionKey 的 “已就绪操作集合”中,并把该 SelectionKey 加入 Selector 的 selected-key 集合,供后续操作使用。

Channel 事件及对应的常量值 Channel 事件 常量值 含义

 

 

 

ConnectSelectionKey.OP_CONNECT 完成与远程服务器的连接(或连接出错)
AcceptSelectionKey.OP_ACCEPT连接被接受(或出错) 
ReadSelectionKey.OP_READ

“读操作”准备就绪

或 已读到数据流的末尾

或 Channel 被连接的另一端关闭

或 出错

WriteSelectionKey.OP_WRITE

“写操作” 准备就绪

或 Channel 被连接的另一端关闭

或 出错

 

如果需要监听多个事件,可以用“或”操作连接相应的常量值。如:

Java代码

 

  1. int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;  

 

SelectionKey

SelectionKey 包含的信息主要有:

  • 监听事件类型集合(Interest Set)

  • 已就绪事件类型集合(Ready Set)

  • Channel

  • Selector

  • 附带的对象(可选)

Interest Set

可通过 SelectionKey.interetOps() 获取该集合,并通过“位操作”来判断是否注册监听了某个事件。

Java代码

 

  1. int interestSet = selectionKey.interestOps();  

  2.   

  3. boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;  

  4. boolean isInterestedInAccept = interestSet & SelectionKey.OP_ACCEPT;  

  5. boolean isInterestedInRead = interestSet & SelectionKey.OP_READ;  

  6. boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;  

 

Ready Set

可通过 SelectionKey.readyOps() 获取该集合。

可以像 Interest Set 那样通过“位操作”来判断某个事件是否已发生。

也可以直接调用 SelectionKey 对象相应的方法来判断(内部原理相同)。

Java代码

 

  1. int readySet = selectionKey.readyOps();  

  2.   

  3. selectionKey.isConnectable();  

  4. selectionKey.isAcceptable();  

  5. selectionKey.isReadable();  

  6. selectionKey.isWritable();  

 

Channel 和 Selector

可通过 SelectionKey 对象相关的方法来获取其对应的 Channel 和 Selector

Java代码

 

  1. Channel channel = selectionKey.channel();  

  2. Selector selector = selectionKey.selector();  

 

附带对象

可将某个对象附带到 SelectionKey 对象上,为后续处理数据提供方便。如,把处理 Channel 数据的 Buffer 对象附带到key上。

Java代码

 

  1. selectionKey.attach(obj);  

  2. Object attachedObj = selectionKey.attachment();  

  3.   

  4. // 也可在注册Selector时指定附带对象  

  5. SelectionKey key = channel.register(selector, SelectionKey.OP_READ, obj);  

 

从 Selector 获取就绪的 Channel

可通过 Selector 对象的以下3个方法之一得知是否有 Channel 已就绪。这三个方法都会返回 int,表示已就绪 Channel 的数量

  • select() 阻塞,直到至少有一个 Channel 就绪

  • select(long timeout) 阻塞,直到至少有一个 Channel 就绪 或 超时

  • selectNow() 非阻塞。无论当前有没有 Channel 就绪,都会立即返回

selectedKeys()

如果上述select方法返回的值大于0,即,有Channel就绪,就可以通过 selector 对象的 selectedKeys() 方法获取这些 Channel 对应的 SelectionKey。再通过 SelectionKey 实例获取 Channel 实例并执行后续操作。

Java代码

 

  1. Set<SelectionKey> selectedKeys = selector.selectedKeys();  

  2. Iterator<SelectionKey> keyIterator = selectedKeys.iterator();  

  3. while (keyIterator.hasNext()) {  

  4.   SelectionKey key = keyIterator.next();  

  5.   // 处理Channel  

  6.   keyIterator.remove();  

  7. }  

 

Selector.wakeup()

如果一个线程调用了 Selector.select() 方法并发生阻塞,此时另一个线程调用了该 Selector 对象的 wakeup() 方法,则前一个被阻塞的线程将会立即返回。

如果调用 wakeup() 时,没有线程阻塞在 select() 方法,则下一个调用 select() 方法的线程将立即返回。除非期间调用了 selectNow() 方法

 

Selector.close()

如果调用了 Selector 对象的 close() 方法,则 Selector 对象会被关闭,其中的 SelectionKey 实例将失效,但 Channel 不会被关闭

 

完整的 Selector 示例

Java代码

 

  1. Selector selector = Selector.open();  

  2. channel.configureBlocking(false);  

  3. SelectionKey key = channel.register(selector, SelectionKey.OP_READ);  

  4. while (true) {  

  5.   int readyChannelCount = selector.selectNow();  

  6.   if (0 == readyChannelCount) {  

  7.     continue;  

  8.   }  

  9.   

  10.   Set selectedKeys = selector.selectedKeys();  

  11.   Iterator keyIterator = selectedKeys.iterator();  

  12.   while(keyIterator.hasNext()) {  

  13.     SelectionKey key = keyIterator.next();  

  14.   

  15.     if(key.isAcceptable()){  

  16.       // 连接已被一个 ServerSocketChannel 接受  

  17.     } else if (key.isConnectable()) {  

  18.       // 已与远程服务器建立连接  

  19.     } else if (key.isReadable()) {  

  20.       // “读操作”已准备就绪  

  21.     } else if (key.isWritable()) {  

  22.       // “写操作”已准备就绪  

  23.     }  

  24.   

  25.     keyIterator.remove();  

  26.   }  

  27. }  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值