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