JAVA 非阻塞IO原理

来源:互联网 发布:应收账款软件标志 编辑:程序博客网 时间:2024/06/05 01:17

1.  基本概念

IO是主存和外部设备(硬盘、终端和网络等)传输数据的过程。IO是操作系统的底层功能实现,底层通过I/O指令进行完成。

2.  nio简介

nio是java New IO的简称(并不只是指非阻塞IO),在jdk1.4里提供的新api。Sun官方标榜的特性如下:
–   为所有的原始类型提供(Buffer)缓存支持。
–   字符集编码解码解决方案。
–   Channel:一个新的原始I/O抽象。
–   支持锁和内存映射文件的文件访问接口。
–   提供多路(non-bloking)非阻塞式的高伸缩性网络I/O。

详细介绍可见 http://www.iteye.com/topic/834447

3.   非阻塞 IO

何为阻塞、何为非阻塞,非阻塞原理。

何为阻塞?

一个常见的网络IO通讯流程如下:

何为非阻塞?
下面有个隐喻:
一辆从 A 开往 B 的公共汽车上,路上有很多点可能会有人下车。司机不知道哪些点会有哪些人会下车,对于需要下车的人,如何处理更好?
1. 司机过程中定时询问每个乘客是否到达目的地,若有人说到了,那么司机停车,乘客下车。 ( 类似阻塞式 )
2. 每个人告诉售票员自己的目的地,然后睡觉,司机只和售票员交互,到了某个点由售票员通知乘客下车。 ( 类似非阻塞 )
很显然,每个人要到达某个目的地可以认为是一个线程,司机可以认为是 CPU 。在阻塞式里面,每个线程需要不断的轮询,上下文切换,以达到找到目的地的结果。而在非阻塞方式里,每个乘客 ( 线程 ) 都在睡觉 ( 休眠 ) ,只在真正外部环境准备好了才唤醒,这样的唤醒肯定不会阻塞。

socket的操作都有一个共同的结构:
1. Read request
2. Decode request
3. Process service
4. Encode reply
5. Send reply
经典的网络服务的设计如下图,在每个线程中完成对数据的处理:


但这种模式在用户负载增加时,性能将下降非常的快。我们需要重新寻找一个新的方案,保持数据处理的流畅,很显然,事件触发机制是最好的解决办法,当有事件发生时,会触动handler,然后开始数据的处理。

Reactor模式类似于AWT中的Event处理:


Reactor模式参与者
1.Reactor 负责响应IO事件,一旦发生,广播发送给相应的Handler去处理,这类似于AWT的thread
2.Handler 是负责非堵塞行为,类似于AWT ActionListeners;同时负责将handlers与event事件绑定,类似于AWT addActionListener

  非阻塞的原理
把整个过程切换成小的任务,通过任务间协作完成。
由一个专门的线程来处理所有的 IO 事件,并负责分发
事件驱动机制:事件到的时候触发,而不是同步的去监视事件。
线程通讯:线程之间通过 wait,notify 等方式通讯。保证每次上下文切换都是有意义的。减少无谓的进程切换。
Reactor就是上面隐喻的售票员角色。每个线程的处理流程大概都是读取数据、解码、计算处理、编码、发送响应
如图:


Java的NIO为reactor模式提供了实现的基础机制,它的Selector当发现某个channel有数据时,会通过SlectorKey来告知我们,在此我们实现事件和handler的绑定。
我们来看看Reactor模式代码:

public class Reactor implements Runnable{  final Selector selector;  final ServerSocketChannel serverSocket;  Reactor(int port) throws IOException {    selector = Selector.open();    serverSocket = ServerSocketChannel.open();    InetSocketAddress address = new InetSocketAddress(InetAddress.getLocalHost(),port);    serverSocket.socket().bind(address);    serverSocket.configureBlocking(false);    //向selector注册该channel     SelectionKey sk =serverSocket.register(selector,SelectionKey.OP_ACCEPT);    logger.debug("-->Start serverSocket.register!");    //利用sk的attache功能绑定Acceptor 如果有事情,触发Acceptor    sk.attach(new Acceptor());    logger.debug("-->attach(new Acceptor()!");  }  public void run() { // normally in a new Thread    try {    while (!Thread.interrupted())    {      selector.select();      Set selected = selector.selectedKeys();      Iterator it = selected.iterator();      //Selector如果发现channel有OP_ACCEPT或READ事件发生,下列遍历就会进行。      while (it.hasNext())        //来一个事件 第一次触发一个accepter线程        //以后触发SocketReadHandler        dispatch((SelectionKey)(it.next()));        selected.clear();      }    }catch (IOException ex) {        logger.debug("reactor stop!"+ex);    }  }  //运行Acceptor或SocketReadHandler  void dispatch(SelectionKey k) {    Runnable r = (Runnable)(k.attachment());    if (r != null){      // r.run();    }  }  class Acceptor implements Runnable { // inner    public void run() {    try {      logger.debug("-->ready for accept!");      SocketChannel c = serverSocket.accept();      if (c != null)        //调用Handler来处理channel        new SocketReadHandler(selector, c);      }    catch(IOException ex) {      logger.debug("accept stop!"+ex);    }    }  }}

以上代码中巧妙使用了SocketChannel的attach功能,将Hanlder和可能会发生事件的channel链接在一起,当发生事件时,可以立即触发相应链接的Handler。

将数据读出后,可以将这些数据处理线程做成一个线程池,这样,数据读出后,立即扔到线程池中,这样加速处理速度:


可参考:

http://www.jdon.com/concurrent/reactor.htm

http://javag.iteye.com/blog/221641

1 0