java NIO到底能解决什么问题

题记:
Java nio是jdk1.4以后才增加的最新特性,这个最新的特性也是作为1.4的最大亮点出现,但是却很少有人建议关注,从而经常出现一些类似我这样的“文盲”,nio有什么作用?
首先一点,nio绝对不会像三鹿一样将一个完整的io程序毁了。其次他改变了老版本io的很难解决的多线程问题,现在可以看出来了,nio主要用于解决多线程的问题,废话少说,上项目!


rel="File-List" href="file:///C:%5CDOCUME%7E1%5C%E7%8E%8B%E5%9B%9B%E6%B5%B7%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml"> rel="Edit-Time-Data" href="file:///C:%5CDOCUME%7E1%5C%E7%8E%8B%E5%9B%9B%E6%B5%B7%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_editdata.mso"> rel="OLE-Object-Data" href="file:///C:%5CDOCUME%7E1%5C%E7%8E%8B%E5%9B%9B%E6%B5%B7%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_oledata.mso">

网元通信相关方案

目前,网元通信采用的方案,局部描述如下:

每个阅读器对应一个Socket链路,对于每个Socket链路,设置有工作线程(收消息线程、发消息线程、消息分发线程),以及消息队列(普通发送队列、紧急发送队列、接受队列),线程与队列相关联:

l         发消息线程负责将消息队列中的消息对象写入Socket

l         收消息线程负责从Socket中读取消息对象放到收消息队列中;

l         消息分发线程负责将消息队列中的消息,分发给预定了此消息的模块(目前的实现是指适配模块)。

如图所示:


这是一个示意性的UML图,绿色模块表示队列、红色模块表示线程。

存在的问题

简单说,就是线程太多。

有关文献表明,Java虚拟机对线程的数目支持是有限度的,大约1000以内。此外,UEP平台在系统开启1000个线程时会自动重启。

显然,目前的实现方案存在问题,每个阅读器三个线程,开启线程太多了。

大概如何解决

修正方案

在当前方案的基础上,减少开启的线程数目:

比如,不区分普通消息、紧急消息,不设置发送队列,将消息同步写入Socket,可以减少两个发送线程。但总要有线程监听、读取Socket上来的数据。

即,每个Socket开一个线程。

还是感觉有点多。

或者,将Socket分组,多个线程在组内轮询多个Socket。实现起来比较麻烦,违反“简单设计”的实践原则。效果也不会特别好。

NIO方案

目前,单独线程管理单条Socket链路,之所以耗费资源的根本原因在于:每个线程以阻塞的方式工作,很可能在Socket链路没有数据传输的时候处于Idle状态,比较浪费;浪费的严重程度取决于处于Idle状态的时间长度。

举一个例子,非常不切实际,但希望能够说明问题。

银行柜台,十位工作人员同时工作,第一位负责接待帐号尾号为0的客户、第二位负责接待帐号尾号为1的客户……依此类推。这样,如果一整天都没有一个帐号尾号为0的客户上门,那么第一位工作人员就打盹儿一整天。

显然效率较低。

就这个例子,更好的模式应该是:一位工作人员负责接待所有客户,在没有客户上门的时候可以打盹儿,有客户上门时,类似Seven-Eleven门口的“欢迎光临”口号把打盹儿中的工作人员惊醒,开始接待工作。或者,客户较多,工作人员非但没有机会打盹儿,客户还总需要排队,就可以稍微多设置几位地位平等工作人员,每次客户上门,随意一位恰好闲着的就负责接待。类似于我们现实世界的情形。

Java NIO,就提供了机制采用后一种模式进行工作。

  Java NIO

概述

NIOJava1.4相对于过去版本的一个较大的功能亮点,N表示New,相关API部署在java.nio.*包中。NIO更多地利用了现代操作系统所提供的底层I/O机制,为我们提供了一种更加高效的I/O解决方案。

Java1.4之前,JavaI/O相关的内容,均由java.io.*包解决,为了描述方便,暂称为“旧IO”。旧IO最重要的概念是流(InputStreamOutputStream)。旧IO以流的方式处理数据。旧IO以阻塞的方式操控流数据。

与本方案相关的,NIO最重要的概念是BufferChannelSelectorNIO以块的方式处理数据。NIO可以按照非阻塞的方式操控数据。

下面简要说明一下NIO的工作模式。(注:仅介绍与本方案相关的NIO内容。)

概念

1.  Buffer:在NIO的世界中,所有数据都在Buffer中,从Buffer中读、写入Buffer。每中基本数据类型都有一个对应的Buffer类,其中ByteBuffer最为常用,也比较特殊——与Channel类联系最为紧密。

Buffer有三个状态变量:positionlimitcapacityBuffer可理解为比较高级的数据,这几个状态变量描述了数组中的数据装载情况,从变量名即可大约知道他们各自是什么意思。详细信息可参考本文末尾罗列的相关参考文献。

2.  Channel:类似于旧IOStreamServerSocketSocket都有对应的Channel类,即ServerSocketChannelSocketChannel,他们之间是双向关联的,即可以互相取得对方的句柄。与Stream不同的是,Channel是双向的。我们需要着重注意的是,SelectableChannel类,顾名思义,该类及其子类,表示“可以被选择”,说得更清楚一些,这样的Channel可以注册到某个Selector对象上,Selector对象可以在Channel关注的某些事件到来的时候,以某种方式给予通知。与著名的Observer模式在思路上惺惺相惜。事实上,我们所关心的ServerSocketChannelSocketChannel都是SelectableChannel的子类。

3.  Selector:可理解为Channel们的调度器,多个Channel可以注册到一个Selector对象,如前所述,SelectorChannel关注的事件发生的时候给予通知。比如,ServerSocketChannel在注册到Selector的时候表示,我关心accept操作,即关心哪些客户端试图与我建链;再比如,SocketChannel就关心read操作,看看什么时候会从Socket上读到数据。Selector提供的select方法是我们最为关注的,该方法是同步的,返回此时Selector收到的所有事件,返回形式是包含多个SelectionKeySet

4.  SelectionKey:包裹SelectorChannel关联关系的类。从SelectionKey中可以得到如下信息:ChannelSelector、操作类型(acceptreadwrite)等。

如何利用NIO解决我们的问题

1.  我们的基本设备:ServerSocketChannel*1Selector*1SocketChannel*NSelectionKey*N、工作线程Worker*1(也可以是由少量线程组成的线程池,即Worker*M)。

2.  开启ServerSocketChannel,并打开某端口,等待客户端接入。

3.  ServerSocketChannel注册到Selector上,表示自己关心accept操作,即关心哪些客户端来请求建链。

4.  Worker启动,while-true调用Selectorselect方法。

5.  有客户端请求上来的时候,select方法会返回相关的SelectionKey对象,从中取得ServerSocketChannel对象,并调用其accept方法获得SocketChannel

6.  把获得的SocketChannel注册到Selector上,同时保存起来,并表示自己关心read操作,即关心何时、从该Socket上读到什么数据。

7.  (此时,while-true调用Selectorselect方法仍旧在马不停蹄地运行着)当有某个Socket有数据上来的时候,select方法会返回相关的SelectionKey对象,从中取得SocketChannel对象,调用read方法读出数据。

可以看出,利用NIO,一个工作线程可以完成所有Socket的数据读取,从根本上解决了上面的问题。当然,如果有需要,也可以设置线程池,或可进一步提高性能。



你可能感兴趣的:(java NIO到底能解决什么问题)