Java nio從jdk1.4開始引入進來的。通常聽到比較多的說法是: 你還沒有用nio啊,你“out”啦;nio不阻塞,傳統的I/O都是阻塞的,效率很低...... 如果你的系統在使用傳統I/O上已經工作得很好且沒有更多的性能等方面的要求,千萬不要盲目地使用nio來重構(也許在你的系統的上下文環境中,使用nio所帶來其它方面的復雜性會提高,處理增多,整體效率并未提高),這至少不符合敏捷開發的態度,簡單說,不要為了nio而nio。當然這不阻礙我們對nio的認識。
Java nio是在Java編程中對I/O的另外一種高級的抽象方式。其對I/O的使用方式也更加貼近操作系統使用I/O的方式:通道和字節緩沖。一般的用法可以參考<<Thinking in java>>。這里我們談談使用nio進行網絡通信編程的特點?;镜腏ava socket編程對小規模的系統可以很好的工作,但是在資源不擴展而用戶請求不斷增大的情況下,系統劣化的比率很大。我們通常會面臨這樣一些這些問題:
1、thread-per-request 的方式,這樣會有更多線程維護和切換的系統開銷,同時在系統擴展性方面受到限制。
2、針對問題1,我們可以采用線程池的方式來節省線程創建、維護以及切換的開銷,但是這樣也會限制系統可以同時處理客戶端的數量,至少對一些長連接協議來說是這樣。此時增加線程池的大小是不能再提高系統性能的,而只會增加系統更多的線程開銷。
3、傳統的socket在讀/寫數據以及建立連接上都是阻塞式的(沒有數據可讀可能會阻塞;沒有足夠空間緩存傳輸的數據可能阻塞;服務端的accept方法以及socket構造函數都會阻塞等待,直到連接建立) 。這些特性會降低整個系統對CPU的利用率以及系統的活躍性,降低系統對客戶請求的響應性及響應時間,在某些情況下是不可接受的甚至會帶來災難性的結果。在這樣的情形下,我們要解決cpu利用率、線程活躍性以及提高系統吞吐量,我們勢必做很多額外的工作且相當復雜。
4、由于傳統socket的阻塞特性,每個對等端都在阻塞等待另外一端完成相關處理,這樣勢必增大死鎖的風險。
5、當然為了提高系統的活躍性,傳統的socket在accept、構造socket以及read方法上都增加了超時控制的處理以及使用socket.close方法來打斷一些阻塞操作,而網絡總是不確定的,設置這些超時時間的設置在具體的上下文環境中的取值也是比較復雜的。簡而言之,為了提高系統的活躍性會增大系統的復雜度。
那么,Java nio真的就是Java socket編程解決以上若干問題的靈丹妙藥嗎?答案是否定的。
nio的非阻塞特性是nio最大的特點,所謂非阻塞無非就是將通信的信道設置成為非阻塞的情況下,對該信道的所有操作都是會立即返回的。如數據讀取,沒有數據的時候會返回0而不是阻塞在信道上。這樣就增大了系統的活躍性,使得系統不必浪費資源的I/O操作上,系統可以更好的利用cpu資源做一些其它處理,在某些場景下會提高系統的響應性以及吞吐量。
同時nio在網絡通信的中采用的網絡I/O事件驅動的方式,即操作系統對用戶感興趣的通信信道及其上面的I/O事件進行監聽并通知應用程序。這實際上也是觀察者模式的一種應用,在這樣的背景下,問題4中的死鎖風險幾乎就沒有了。
如果你開發的服務端不是迭代服務器(順序化處理每個客戶請求),那么對于問題1、2在nio服務器中也是一樣的。換句話說,我們開發的并發處理服務器為了實現對各種網絡I/O事件的處理,并對每種我們所關心的網絡I/O事件進行及時響應,采用thread-per-request或者線程池的方式是無法避免的。所以很多人會感覺nio的網絡編程結構與非nio的網絡編程結構實際是一樣的,accept換成了select,監聽連接入站編程了監聽信道上感興趣的I/O事件,為了提高系統的處理能力,還是要啟動異步的線程來處理信道上的網絡I/O事件,只是具體實現不一樣而已。這說明nio并沒有改變我們服務端程序編寫的整體結構,只是在nio的環境下,我們的確可以去提高系統的活躍性、響應性和吞吐量。nio與非nio在讀/寫網絡數據以及連接建立等網絡操作上是沒有多大區別的,這些因素主要都還是取決于網絡狀況、操作系統協議棧實現、應用自身處理網絡數據的方式等多個方面。甚至,我們在享用nio的同時在一定程度上還會增大了網絡編程的復雜度,因為數據的讀寫以及連接的建立等操作變得更加不確定,當然最終網絡編程的復雜還是取決于協議的復雜度。