在Java1.4之前的版本,Java對I/O的支持并不完善,開發人員在開發高性能I/O程序的時候,會面臨以下幾個問題:
1、沒有數據緩存區,I/O性能存在問題
2、沒有C/C++通道的概念,輸入和輸出流是相互獨立的不能復用
3、同步阻塞式I/O通信(BIO),造成線程資源被長時間阻塞(致命缺陷)
4、硬件可移植性差,支持的字符集編碼有限
根據UNIX網絡編程的概念,I/O模型有五種
詳情請查看這篇文章
UNIX五種網絡I/O模型
在沒有JavaNIO之前呢,基于Java的所有的所有的Socket通信都采用了同步阻塞式模型(BIO)這種請求<->應答的通信模式降低了開發難度,但是在性能和可靠性方面存在巨大缺陷,因此大型應用服務器都是C/C++語言開發,因為他們可以直接操作系統提供的異步的I/O或者是AIO能力。當并發訪問量增大,響應時間延遲增大之后,采用JavaBIO開發的服務器只能通過硬件擴容來滿足高并發和低延時,極大增加了企業成本。
正是由于JavaBIO的不足,Java1.4版本之后提供了新的NIO庫,支持非阻塞式I/O操作。
JavaNIO提供了很多的異步I/O開發和API類庫,主要的類和接口如下:
進行異步I/O操作的緩存區ByteBuffer
進行異步I/O操作的管道Pipe
進行各種I/O操作(異步或者同步)的Channel,包括ServerSocketChannel和SocketChannel
多種字符集編碼能力和解碼能力
實現非阻塞I/O操作的選擇器Selector
基于流行Perl實現的正則表達式庫
?
?了解NIO必須先了解幾個概念
什么是同步 |
什么是異步 |
什么是阻塞 |
什么是非阻塞 |
什么是同步阻塞 |
什么是同步非阻塞 |
什么是異步阻塞 |
什么是異步非阻塞 |
?
?
?
?
?
?
?
?
技術語言解釋:
?同步:當一個進程去訪問另一個進程時,必須得到一個結果(指的是用戶進程觸發IO操作并等待或者輪詢的去查看IO操作是否就緒)
異步:當一個進行訪問另一個進程時時,不用立刻給結果,我會告訴你一個信息,你把結果放在這里,通知我即可(異步I/O就是Java將I/O讀寫操作委托給操作系統,告訴操作系統將數據放到哪個緩存區,數據大小)
阻塞:當一個進程去操作某個資源時發現這個資源不在或者暫時不可操作,那么這個進程就一直等,等可以操作
非阻塞:當進程發現沒有資源可操作的時候立刻返回讀寫函數,而不會等待
生活中的例子:
同步:比如電話好友,那么無論打通還是打不通,都會得到回應,(用戶接通了,用戶正在通話,用戶已關機等信息)
異步:比如叫餐,我們APP下單之后就可以做其他的事情,你只需要告訴地址,電話,食物到了送餐員會打電話通知你
阻塞:比如收費站堵車,這個就最能代表阻塞,你只能等著啥干不了
非阻塞:比如到銀行排隊辦理業務,那么我們會拿一張排號小票,就可以去做其他事情,到號就會叫你!
網上有一個例子
同步阻塞:到飯店吃飯點餐,在那里等著不停的問,菜好了嗎?菜好了嗎?
同步非阻塞:點完餐,直接去打球,不過你需要隔一段時間來問一次,菜好了嗎?
異步阻塞:菜做好飯店打電話給你讓你親自去拿。
異步非阻塞:菜做好,飯店直接送到球場
所以我們歸納:
同步和異步是針對應用程序和內核的交互而言的,
1、同步指的是用戶進程觸發I/O操作并等待或者輪詢的去查看I/O操作是否就緒,
2、異步是指用戶進程觸發I/O操作以后便開始做自己的事情,而當I/O操作已經完成的時候會得到I/O完成的通知。
阻塞和非阻塞是針對于進程在訪問數據的時候,根據I/O操作的就緒狀態來采取的不同方式,說白了是一種讀取或者寫入操作函數的實現方式,
1、阻塞方式下讀取或者寫入函數將一直等待
2、而非阻塞方式下,讀取或者寫入函數會立即返回一個狀態值。?
所以,I/O操作可以分為3類:同步阻塞(即早期的I/O操作)、同步非阻塞(NIO)、異步(AIO)。?
?
BIO
在JDK1.4之前,用Java編寫網絡請求,都是建立一個ServerSocket,然后,客戶端建立Socket時就會詢問是否有線程可以處理,如果沒有,要么等待,要么被拒絕。即:一個連接,要求Server對應一個處理線程。
NIO
在Java里的由來,在JDK1.4及以后版本中提供了一套API來專門操作非阻塞I/O,我們可以在java.nio包及其子包中找到相關的類和接口。由于這套API是JDK新提供的I/O API,因此,也叫New I/O,這就是包名nio的由來。這套API由三個主要的部分組成:緩沖區(Buffers)、通道(Channels)和非阻塞I/O的核心類組成。在理解NIO的時候,需要區分,說的是New I/O還是非阻塞IO,New I/O是Java的包,NIO是非阻塞IO概念。這里講的是后面一種。
NIO本身是基于事件驅動思想來完成的,其主要想解決的是BIO的大并發問題: 在使用同步I/O的網絡應用中,如果要同時處理多個客戶端請求,或是在客戶端要同時和多個服務器進行通訊,就必須使用多線程來處理。也就是說,將每一個客戶端請求分配給一個線程來單獨處理。這樣做雖然可以達到我們的要求,但同時又會帶來另外一個問題。由于每創建一個線程,就要為這個線程分配一定的內存空間(也叫工作存儲器),而且操作系統本身也對線程的總數有一定的限制。如果客戶端的請求過多,服務端程序可能會因為不堪重負而拒絕客戶端的請求,甚至服務器可能會因此而癱瘓。
NIO基于Reactor,當socket有流可讀或可寫入socket時,操作系統會相應的通知引用程序進行處理,應用再將流讀取到緩沖區或寫入操作系統。
也就是說,這個時候,已經不是一個連接就要對應一個處理線程了,而是有效的請求,對應一個線程,當連接沒有數據時,是沒有工作線程來處理的。
AIO
與NIO不同,當進行讀寫操作時,只須直接調用API的read或write方法即可。這兩種方法均為異步的,對于讀操作而言,當有流可讀取時,操作系統會將可讀的流傳入read方法的緩沖區,并通知應用程序;對于寫操作而言,當操作系統將write方法傳遞的流寫入完畢時,操作系統主動通知應用程序。
即可以理解為,read/write方法都是異步的,完成后會主動調用回調函數。
在JDK1.7中,這部分內容被稱作NIO.2,主要在java.nio.channels包下增加了下面四個異步通道:
AsynchronousSocketChannel
AsynchronousServerSocketChannel
AsynchronousFileChannel
AsynchronousDatagramChannel
其中的read/write方法,會返回一個帶回調函數的對象,當執行完讀取/寫入操作后,直接調用回調函數。
?