JAVA 非阻塞IO原理

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

發佈了54 篇原創文章 · 獲贊 4 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章