您的位置:首页 > 编程语言 > Java开发

Java NIO中的Selector和IO复用

2016-07-16 15:34 225 查看
转自:http://www.molotang.com/articles/906.html

前面讲到JDK1.4中NIO中的Buffer和Channel通过缓冲机制已经在提高IO效率上做了很多优化,那么把IO复用和这些结合起来,将进一步优化对IO相关程序的设计,提高IO上的使用效率。

0. Selection的概念和意义

在《Java NIO》一书里介绍readiness selection概念是举了一个很好的例子,结合我们今天去银行办理个人业务的情况,我描述讲解一下。虽然我们现在的网银和其它互联网工具已经很发达了,但免不了我们需要去工行、招行办个业务啥的。因为银行网点多、服务网点门店小,而需要办理业务的人又很多,就不得不排队等待,那么常见的有两种方式,比如3个服务窗口:

可以3个窗口分别排队,为了方便队伍秩序维护,站到某一队的人不得到其它队伍插队,相互分隔开来,每一个队伍单独维护着先后顺序

另一种方式是当你走进招商银行正门,服务员给你打一张号码条,即不分窗口进行全局排队,无论服务窗口有几个都是公用的,只要全局排队号码在前面的人已经完成了服务,则下一个号码的人就可以去空闲窗口去办理业务

这个描述大概说了下不使用和使用readiness selection的情况,可能在效率体现上也未必完全恰当,但在计算机编程上,readiness selection和IO复用在特定场景下很大的提高了效率。我之前整理过Java并发的文章,有一个观点就是,并发并不是线程越多越好,一方面这需要很大的维护成本,更重要的是我们的计算机处理资源都是非常有限的,多开一个线程就多耗费一些资源,所以我们可能会考虑使用一个线程熟练有上限的线程池。那么如果每个线程负责处理一个网络连接,线程占用达到上限的时候,新的连接又将如何处理?已被之前连接占用的线程始终不被释放,这样调度是否是最高效的?在Java中,Selector和IO复用很好的解决了这个问题。

其实Java中的Selector和IO复用是基于各个操作系统平台实现的。在操作系统底层的API中,也早有select的概念,有select()、poll()等函数。

随着Ajax技术和Web长连接推送应用场景的发展,NIO和IO复用有了很大的需求。在Java开源项目的Jetty和Tomcat服务器实现中,也都对NIO的IO复用机制做了很大支持。

在readiness selection的设计和实现中,有三个重要角色SelectableChannel、SelectionKey和Selecotor。





Java中readiness selection中SelectableChannel、SelectionKey和Selector之间的关系

1. SelectableChannel

在之前的Java文章中,我已经将Buffer和Channel的概念整理介绍出来了,但关于Channel的分类和具体使用细节,暂时就不准备过度赘述了,这里说下SelectableChannel。在Java第4版的API中,Channel的子类分为两大块:

FileChannel,针对文件IO的Channel,可以通过FileInputStream、FileOutputStream和RandomAccessFile来获得,不支持非阻塞模式,进而也就不支持readiness selection

SelectableChannel,除File以外,像对Socket IO做支持的Channel都属于SelectableChannel,支持非阻塞模式和readiness selection

我们这里讲的readiness selection就需要SelectableChannel在非阻塞模式下使用,可以通过

这个方法进行配置。从上面的关系图中我们可以看到Channel最终是要和Selector关联起来使用的,实际上是通过SelectableChannel中的register()方法进行注册的。

第二个参数是感兴趣的事件,默认常量有4个(连接、接受、读、写),定义在SelectionKey类中,但并不是所有Channel都一定支持,可以用validOps()判断。除此之外,同一SelectableChannel对象可以注册到多个Selector,可以调用它的keyFor()方法,来得到对应的SelectionKey。

2. SelectionKey

接下来说说在Channel和Selector之间的关联对象SelectionKey。既然是关联对象,那肯定是可以得到连接的两个对象的:

还有支持的感兴趣的事件,以及已经准备好IO的事件,感兴趣的事件的方法是同名重载,一个为get另一个为set:

Selection中维护了两个Set集合,正如上面方法中所示,一个是感兴趣的事件集合,另一个是准备好了的,可以进行IO操作的集合。

对于SelectionKey的cancel()方法需要注意的是,并不直接生效,而是到Selector下次select()时,但SelectionKey的isValid()会立即回复false。

3. Selector

终于,最重要的对象出现了。通常,Selector是由静态工厂方法open()实例化的,也可以直接调用SelectorProvider的openSelector()返回,Selector的provider()方法会返回特定的provider对象。用完了调用close()以释放资源,可以用isOpen()判断Selector是否已经关闭。

当Selector和特定的SelectableChannel关联好了,开始工作了,那么就需要进行select操作,如上面方法所示。

select() 阻塞调用线程,直到有某个Channel的某个感兴趣的Op准备好了

select(long) 阻塞调用线程,但超时会自动返回

selectNow() 则不阻塞

wakeup() 则是从另一线程对Selector调用,恢复调用select()的线程执行;注意这这是取消最近一次的调用,如果还没有调用,则下一次调用会直接返回

select()只返回本次执行select时从未准备好到准备好状态的channel数,如果不为0,将调用如下方法进行处理。

这个方法返回一个包含SelectionKey对象的集合,分别对应各个准备好的Channel。而对于注册在这个Selector的所有Key,还有一个方法可以获取到。

Selector对象维护了3个key集合,一个注册过的,一个是选择过的,最后一个是cancel过但是未反注册的,这个我们没有方法直接获取到。

4. 常规使用示例

了解过了这3个重要角色,看一段常规使用的代码示例。

这是一个简单服务器接受请求,并做读取的代码逻辑。

这句是通过Channel和Buffer进行数据读取处理。

注意最后的:

这行代码是必要的。

5. ReadinessSelection注意点和IO复用

为了解释为什么上面实例中最后的iterator的remove()调用是必要的,我们需要先来看下Java在select实现上的原理和过程。

首先,针对关联每个Channel的SelectionKey对象,都维护者2个Set集合,分别是

interestOps

readyOps

然后,每个Selector又维护着3个Set集合,分别是

registeredKeys,可以通过keys()方法获得

selectedKeys

cancelledKeys,存储着调用过cancel()方法,但并没有被反注册或者说解开注册的SelectionKey对象,没有方法直接获得

每次select()方法调用时,先把cancelledKeys数据同步到registerKeys和selectedKeys,做减法以完成反注册,接下来调用操作系统底层的select实现,重点在于阻塞之后得到的结果处理:

如果有在registeredKeys中的key的感兴趣事件发生了,检查是否该key存在于selectedKeys中,如果没有,则将该key的readOps清空,根据此次的情况进行重新设置,并将key加入到selectedKeys

如果不是上面这种情况,即selectedKeys中已经包含了事件中的key,那么只做“从无到有”的更新操作,这里的所谓“从无到有”就是如果原来已经有了的key不做自动移除,key对应的readOps也只是将之前没有ready而此次ready的放进去,不会将之前ready而此时已经非ready的做更新

说道这里,remove()的必要性就不必多解释了,在select()返回之前,再将阻塞过程当中发生cancel的key做一次同步。

上面提到了几个集合,其实Selector对象本身的操作是线程安全的,但3个keySet是可能随时变化的,可以获取到再进行更改,这个Set的使用需要额外做同步来保证线程安全。

其实,大多数情况下使用Selector的select()只需单线程就可以满足了,而对于select得到的channel和对应的IO操作,可以新开线程或者使用线程池来处理。这也正是IO复用的意义所在。

关于Java NIO中Selector的使用,本文件就解释到这里。更简洁的NIO使用介绍可以参看这里:

http://ifeve.com/java-nio-all/

关于操作系统的select()、poll()和epoll可以参看:

http://www.cnblogs.com/bigwangdi/p/3182958.html

http://blog.csdn.net/tianmohust/article/details/6677985
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: