Java I/O模型从BIO到NIO和Reactor模式
本文轉發自技術世界,原文鏈接 http://www.jasongj.com/java/nio_reactor/
一、Java I/O模型
同步I/O 每個請求必須逐個地被處理,一個請求的處理會導致整個流程的暫時等待,這些事件無法并發地執行。用戶線程發起I/O請求后需要等待或者輪詢內核I/O操作完成后才能繼續執行。
多個請求可以并發地執行,一個請求或者任務的執行不會導致整個流程的暫時等待。用戶線程發起I/O請求后仍然繼續執行,當內核I/O操作完成后會通知用戶線程,或者調用用戶線程注冊的回調函數。
-
阻塞 某個請求發出后,由于該請求操作需要的條件不滿足,請求操作一直阻塞,不會返回,直到條件滿足。
-
非阻塞 請求發出后,若該請求需要的條件不滿足,則立即返回一個標志信息告知條件不滿足,而不會一直等待。一般需要通過循環判斷請求條件是否滿足來獲取請求結果。
需要注意的是,阻塞并不等價于同步,而非阻塞并非等價于異步。事實上這兩組概念描述的是I/O模型中的兩個不同維度。
同步和異步著重點在于多個任務執行過程中,后發起的任務是否必須等先發起的任務完成之后再進行。而不管先發起的任務請求是阻塞等待完成,還是立即返回通過循環等待請求成功。
而阻塞和非阻塞重點在于請求的方法是否立即返回(或者說是否在條件不滿足時被阻塞)。
二、Unix下五種I/O模型
Unix 下共有五種 I/O 模型:
- 阻塞 I/O
- 非阻塞 I/O
- I/O 多路復用(select和poll)
- 信號驅動 I/O(SIGIO)
- 異步 I/O(Posix.1的aio_系列函數)
如上文所述,阻塞I/O下請求無法立即完成則保持阻塞。阻塞I/O分為如下兩個階段。
- 階段1:等待數據就緒。網絡 I/O 的情況就是等待遠端數據陸續抵達;磁盤I/O的情況就是等待磁盤數據從磁盤上讀取到內核態內存中。
- 階段2:數據拷貝。出于系統安全,用戶態的程序沒有權限直接讀取內核態內存,因此內核負責把內核態內存中的數據拷貝一份到用戶態內存中。
非阻塞I/O請求包含如下三個階段
- socket設置為NONBLOCK(非阻塞)就是告訴內核,當所請求的I/O操作無法完成時,不要將線程睡眠,而是返回一個錯誤碼(EWOULDBLOCK),這樣請求就不會阻塞。
- I/O操作函數將不斷的測試數據是否已經準備好,如果沒有準備好,繼續測試,直到數據準備好為止。整個I/O 請求的過程中,雖然用戶線程每次發起I/O請求后可以立即返回,但是為了等到數據,仍需要不斷地輪詢、重復請求,消耗了大量的 CPU 的資源。
- 數據準備好了,從內核拷貝到用戶空間。
一般很少直接使用這種模型,而是在其他I/O模型中使用非阻塞I/O 這一特性。這種方式對單個I/O 請求意義不大,但給I/O多路復用提供了條件。
I/O多路復用會用到select或者poll函數,這兩個函數也會使線程阻塞,但是和阻塞I/O所不同的是,這兩個函數可以同時阻塞多個I/O操作。而且可以同時對多個讀操作,多個寫操作的I/O函數進行檢測,直到有數據可讀或可寫時,才真正調用I/O操作函數。從流程上來看,使用select函數進行I/O請求和同步阻塞模型沒有太大的區別,甚至還多了添加監視Channel,以及調用select函數的額外操作,增加了額外工作。但是,使用 select以后最大的優勢是用戶可以在一個線程內同時處理多個Channel的I/O請求。用戶可以注冊多個Channel,然后不斷地調用select讀取被激活的Channel,即可達到在同一個線程內同時處理多個I/O請求的目的。而在同步阻塞模型中,必須通過多線程的方式才能達到這個目的。調用select/poll該方法由一個用戶態線程負責輪詢多個Channel,直到某個階段1的數據就緒,再通知實際的用戶線程執行階段2的拷貝。 通過一個專職的用戶態線程執行非阻塞I/O輪詢,模擬實現了階段一的異步化。
首先我們允許socket進行信號驅動I/O,并安裝一個信號處理函數,線程繼續運行并不阻塞。當數據準備好時,線程會收到一個SIGIO 信號,可以在信號處理函數中調用I/O操作函數處理數據。
調用aio_read 函數,告訴內核描述字,緩沖區指針,緩沖區大小,文件偏移以及通知的方式,然后立即返回。當內核將數據拷貝到緩沖區后,再通知應用程序。所以異步I/O模式下,階段1和階段2全部由內核完成,完成不需要用戶線程的參與。
除異步I/O外,其它四種模型的階段2基本相同,都是從內核態拷貝數據到用戶態。區別在于階段1不同。前四種都屬于同步I/O。
三、Java中四種I/O模型
上一章所述Unix中的五種I/O模型,除信號驅動I/O外,Java對其它四種I/O模型都有所支持。其中Java最早提供的blocking I/O即是阻塞I/O,而NIO即是非阻塞I/O,同時通過NIO實現的Reactor模式即是I/O復用模型的實現,通過AIO實現的Proactor模式即是異步I/O模型的實現。
從IO到NIO
- 面向流 vs. 面向緩沖
Java IO是面向流的,每次從流(InputStream/OutputStream)中讀一個或多個字節,直到讀取完所有字節,它們沒有被緩存在任何地方。另外,它不能前后移動流中的數據,如需前后移動處理,需要先將其緩存至一個緩沖區。
Java NIO面向緩沖,數據會被讀取到一個緩沖區,需要時可以在緩沖區中前后移動處理,這增加了處理過程的靈活性。但與此同時在處理緩沖區前需要檢查該緩沖區中是否包含有所需要處理的數據,并需要確保更多數據讀入緩沖區時,不會覆蓋緩沖區內尚未處理的數據。
- 阻塞 vs. 非阻塞
Java IO的各種流是阻塞的。當某個線程調用read()或write()方法時,該線程被阻塞,直到有數據被讀取到或者數據完全寫入。阻塞期間該線程無法處理任何其它事情。Java NIO為非阻塞模式。讀寫請求并不會阻塞當前線程,在數據可讀/寫前當前線程可以繼續做其它事情,所以一個單獨的線程可以管理多個輸入和輸出通道。
- 選擇器(Selector)
Java NIO的選擇器允許一個單獨的線程同時監視多個通道,可以注冊多個通道到同一個選擇器上,然后使用一個單獨的線程來“選擇”已經就緒的通道。這種“選擇”機制為一個單獨線程管理多個通道提供了可能。
- 零拷貝
Java NIO中提供的FileChannel擁有transferTo和transferFrom兩個方法,可直接把FileChannel中的數據拷貝到另外一個Channel,或者直接把另外一個Channel中的數據拷貝到FileChannel。該接口常被用于高效的網絡/文件的數據傳輸和大文件拷貝。在操作系統支持的情況下,通過該方法傳輸數據并不需要將源數據從內核態拷貝到用戶態,再從用戶態拷貝到目標通道的內核態,同時也避免了兩次用戶態和內核態間的上下文切換,也即使用了“零拷貝”,所以其性能一般高于Java IO中提供的方法。
總結
以上是生活随笔為你收集整理的Java I/O模型从BIO到NIO和Reactor模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2021当代青年婚恋状态研究报告
- 下一篇: List list=new ArrayL