我們來繼續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方法來分配, 如:
- CharBuffer buf = CharBuffer.allocate(1024);
看一個簡單例子:
稍微提及一下java的基本IO操作模型,以文件copy爲例:
-
開啓輸入流將文件讀入內存:輸入數據先進入Kernel區域,再copy到JVM
-
開啓輸出流將內存中的數據輸出到另一個文件: 先由JVM cop到Kernel再到終端
ByteBuffer
其中ByteBuffer又分爲HeapByteBuffer與DirectByteBuffer.
顯然,一個是在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, 哈哈。
本文有些圖片解釋參考自:
http://tutorials.jenkov.com/java-nio/overview.html