Deep Dive 3 - NIO


我們來繼續80後Deep Dive 3 - NIO。


Java NIO(New IO)是Java 1.4中引入的,時間的話已經是2002年了,確實久遠。NIO的全稱是New IO,作者偷懶就直接稱之爲NIO, 反而聽起來酷酷的。


1. NIO 之父


老樣子,我們先看看NIO的作者,NIO之父Mark Reinhold。


Mark大叔畢業於MIT Ph.D.,老SUN員工了。大叔1996年加入Sun,一待就是13年,全心開發Java,後來Oracle於2010年收購SUN,被迫變成Oracle員工,擔任Java首席架構師一直到現在,說白了一直從事Java開發19年了,不對是“一直開發Java19年”, 區別大了。

大牛有一段自我介紹中提及了Sun,很有意思, 只可意會。

Prior to working at Oracle I did much the same thing at Sun Microsystems, a great company which was too lucky for its own good during the boom years and subsequently driven into the ground by a false prophet who was long on vision and short on execution.

http://mreinhold.org/

不小心故意瞅了一眼大師的朋友圈,好傢伙,果然大師的朋友圈都是大神。


看到第一個了麼?Java之父啊。沒個XXO頭銜都不好意思加好友。


2. NIO 概述



可以看出NIO核心主要提供了Channel與Buffer,以及隱含在包中的異步IO與Selector。


  • Channel與Buffer : 標準的IO是基於字節流與字符流操作的, 而NIO是基於Channel通道與Buffer緩衝區的。
  • Non-Blocking IO  : NIO可以做到非阻塞IO(除文件IO),如線程可以Channel讀取數據到Buffer, 同時還可以並行做其他事情;當數據寫入緩衝區後可以繼續;
  • Selector :選擇器Selector可以用來監聽多個事件,如單個線程監聽多個數據通道等。

2.1 Channel


Channel接口定義很清爽,少到幾乎不看注視不知道幹嘛。好吧,註釋有云:Channel代表了與一個實體對象entity的連接,如文件,網絡socket, 甚至硬件設備等。

大體來說,Channel的目的是爲其子類及實現支持併發多線程訪問的安全環境。Channel有點類似我們熟悉的流,數據可以與Buffer進行雙向交互。

如圖:(圖來自jenkov.com/)


主要實現有以下類涵蓋了文件,TCP, UDP.

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel


2.2 Buffer


Buffer簡單來說是一塊可被讀寫的內存數據塊,封裝成Buffer對象用來交互。


Buffer根據數據類型又分成如上幾種。Buffer有幾個核心屬性我們需要了解一下。

上圖:


首先,Buffer區分讀模式與寫模式。


  • Capacity 即其Buffer的總容量;單位爲當前數據類型,如1024 capacity long數據。可以在其滿了以後,清空(讀數據/清除)。
  • Position 請看圖即可;No? 寫模式就是當前寫的位置,初始爲0, 並隨着數據的寫入而動態變化;通過flip切換到讀模式時,首先重置0,當讀取數據時,position會動態移動到下一個可讀位置。
  • Limit    寫模式下表示最多能寫多少數據,等於capacity;切換到讀模式後,limit會設置成爲寫模式的position位置,即可以讀到之前寫入的所有數據;
  • flip()    將Buffer從寫模式切換到讀模式,即會將position設爲0, 並將limit設置爲之前的position值。

Buffer 可以通過allocate方法來分配, 如:

  1. CharBuffer buf = CharBuffer.allocate(1024);  

看一個簡單例子:


稍微提及一下java的基本IO操作模型,以文件copy爲例:

  • 開啓輸入流將文件讀入內存:輸入數據先進入Kernel區域,再copy到JVM

  • 開啓輸出流將內存中的數據輸出到另一個文件: 先由JVM cop到Kernel再到終端

ByteBuffer

其中ByteBuffer又分爲HeapByteBufferDirectByteBuffer.



顯然,一個是在java堆中分配空間,一個是在c heap中分配空間,即非gc託管,無法進行垃圾回收。所以,c heap的buffer需要手工釋放。

所以,按照我們上文提及的基本IO操作模型,direct buffer顯然省掉了jvm copy這個過程,速度會加快。

另外,稍微提及一下,Off-Heap Buffer經常用來做大對象緩存, 爲什麼? 因爲這樣可以脫離GC管理,不被垃圾回收掉。


2.2 Non-Blocking iO

Blocking v.s. Non-Blocking:  阻塞IO是在調用某方法時線程是處於阻塞的,它會一直等待數據返回或者超時才返回,如InputStream.read();  ServerSocket.accept(); 說的再細點的話,結合操作模型,程序首先發送請求給內核kernel,然後內核去進行網路通信,如果是read(), 在內核準備好數據前,這個線程一直會被掛起,一直等待。

詳細過程如下:


有了上面的鋪墊,則對應的非阻塞則好理解了:


一個明顯區別就是,當發起第一次call請求後,Non-Blocking的線程並沒有被阻塞,但它也沒有去做別的事,而是不斷的發起call請求去檢測等待返回。這樣的機制,其實只用一個線程來監聽,其它線程都可以去做其他事情, 從而引出了NIO中的selector機制。


Selector選擇機制

Selector(選擇器)是Java NIO中能夠檢測一到多個NIO通道,並能夠知曉通道是否爲諸如讀寫事件做好準備的組件。這樣,一個單獨的線程可以管理多個channel,從而管理多個網絡連接。 


雙向通道channel上我們可以註冊我們感興趣的事件:


事件名 對應值
服務端接收客戶端連接事件 SelectionKey.OP_ACCEPT(16)
客戶端連接服務端事件 SelectionKey.OP_CONNECT(8)
讀事件 SelectionKey.OP_READ(1)
寫事件 SelectionKey.OP_WRITE(4)


代碼如下:


值得注意的是,NIO中只支持SocketChannel與ServerSocketChannel, FileChannel是不能實現NIO的。但我們知道上面的io與內核的交互以及DirectByteBuffer,仍然有辦法加速文件的io。

2.4 AIO (Asynchronous I/O) = NIO 2.0

Java 1.7又引入了AIO, 即異步IO。細心的讀者觀察到,上面Non-Blocking的線程並沒有被阻塞,但它也沒有去做別的事,而是不斷的發起call請求去檢測等待返回。而可以更佳智能就好了。

想到以前看過的一本書的例子,以物流快遞爲例,名字忘記了。

  • BlockingIO : 你要去物流快遞公司等貨,並且不能離開,一直等下去。

  • Non-Blocking:你只要每天去物流快遞公司看一下,甚至一個小區可以派一個人去看看整個小區有沒有快遞,如selector模式。

  • Asynchronous I/O:快遞來了, 您只要簽收就好。


好吧, 原來快遞行業玩的也是高大上的AIO, 哈哈。


好吧,時間不早了,要上班去了。寫太多看起來也不方便。拋磚引玉,希望對大家有幫助。

公衆號:技術極客TechBooster


本文有些圖片解釋參考自:

http://tutorials.jenkov.com/java-nio/overview.html


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章