Java io的接口經(jīng)過了多次迭代吼砂,現(xiàn)在主要分為三部分,即java IO (since JDK1.0),java NIO(since JDK1.4),java NIO2(since JDK1.7)。
那么java為什么要將IO的接口分成這三部分呢瞧剖,這三部分的接口又有什么不同呢墅垮?
它們背后的底層原理是什么,要怎么樣理解才比較好呢意鲸?常常聽到的阻塞烦周,非阻塞,同步怎顾,異步的概念读慎,到底它們是什么,之間有什么區(qū)別槐雾?
本文將試著根據(jù)相關(guān)的文檔夭委,用自己的語言來解釋這些問題,希望對你有所啟發(fā)募强。
一 涉及的對象
1) 用戶進(jìn)程和操作系統(tǒng)內(nèi)核
Java 中的IO可以理解為是在Java程序和操作系統(tǒng)內(nèi)核兩個(gè)對象之間進(jìn)行的株灸。
后面所說的阻塞和非阻塞,同步和異步都是這兩個(gè)對象相互作用的結(jié)果擎值。在本文中慌烧,用戶進(jìn)程指的就是Java程序。
2) 程序空間和內(nèi)核空間
Waiting for the data to be ready(等待數(shù)據(jù)到達(dá)內(nèi)核緩沖區(qū))
Copying the data from the kernel to the process(從內(nèi)核緩沖區(qū)拷貝數(shù)據(jù)到程序緩沖區(qū))
在Linux中鸠儿,對于一次讀取IO的操作屹蚊,數(shù)據(jù)并不會(huì)直接拷貝到程序的程序緩沖區(qū)厕氨。
它首先會(huì)被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中,然后才會(huì)從操作系統(tǒng)內(nèi)核的緩沖區(qū)拷貝到應(yīng)用程序的緩沖區(qū)汹粤。
程序空間:分配給用戶程序的內(nèi)存空間命斧。
內(nèi)核空間:內(nèi)核擁有的內(nèi)存空間。
3) 阻塞和非阻塞
阻塞:用戶進(jìn)程進(jìn)行系統(tǒng)調(diào)用后嘱兼,用戶進(jìn)程一直處于鎖定的狀態(tài)冯丙,不能進(jìn)行其他操作
非阻塞:用戶進(jìn)程進(jìn)行系統(tǒng)調(diào)用后,用戶進(jìn)程沒有被鎖定遭京,可以進(jìn)行其他操作
阻塞和非阻塞說的是用戶進(jìn)程的狀態(tài)胃惜,即用戶進(jìn)程是否被鎖定
4) 同步和異步
A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes.
An asynchronous I/O operation does not cause the requesting process to be blocked.
一個(gè)同步的io操作會(huì)導(dǎo)致發(fā)起請求的進(jìn)程阻塞直到這個(gè)io操作完成。一個(gè)異步的io操作不會(huì)導(dǎo)致請求的線程被阻塞哪雕。
同步:用戶線程和io線程做同一件事(用戶線程被阻塞船殉,等待內(nèi)核返回處理結(jié)果)
異步:用戶線程和io線程做不同的事情(用戶線程不被阻塞,做其他的事情斯嚎,內(nèi)核處理完成發(fā)送結(jié)果給用戶線程)
5) 文件描述符
在Linux下面一切皆文件利虫。文件描述符(file descriptor)是內(nèi)核為文件所創(chuàng)建的索引,所有I/O操作都通過調(diào)用文件描述符(索引)來執(zhí)行堡僻,包括下面我們要提到的socket糠惫。Linux剛啟動(dòng)的時(shí)候會(huì)自動(dòng)設(shè)置0是標(biāo)準(zhǔn)輸入,1是標(biāo)準(zhǔn)輸出钉疫,2是標(biāo)準(zhǔn)錯(cuò)誤硼讽。
二 uninx五種IO模型
1) blocking IO(阻塞IO)
調(diào)用過程:
用戶進(jìn)程調(diào)用一個(gè)recvfrom請求,內(nèi)核收到這個(gè)請求之后沒有立即返回牲阁,而是首先等待數(shù)據(jù)到達(dá)固阁。
等數(shù)據(jù)到達(dá)之后,再進(jìn)行數(shù)據(jù)拷貝城菊,將數(shù)據(jù)從內(nèi)核緩存區(qū)拷貝到用戶進(jìn)程緩存區(qū)备燃,拷貝完成后返回給進(jìn)程ok的消息。
上面整個(gè)過程都是阻塞的凌唬。
2) nonblocking IO(非阻塞IO)
調(diào)用過程:
用戶進(jìn)程調(diào)用一個(gè)recvfrom請求并齐,內(nèi)核收到這個(gè)請求之后立即返回?cái)?shù)據(jù)沒有到達(dá)的消息。
用戶進(jìn)程收到消息后繼續(xù)發(fā)送recvfrom請求客税,如果數(shù)據(jù)仍沒有到達(dá)况褪,內(nèi)核繼續(xù)返回沒有到達(dá)的消息。
如此循環(huán)霎挟,直到數(shù)據(jù)到達(dá)內(nèi)核窝剖。然后內(nèi)核將收到的數(shù)據(jù)從內(nèi)核緩存區(qū)拷貝到用戶進(jìn)程緩存區(qū),拷貝完成后返回給進(jìn)程ok的消息酥夭。
上面整個(gè)過程都是阻塞的赐纱。
3) IO multiplexing(IO復(fù)用) (select/poll/epoll)
調(diào)用過程:
用戶進(jìn)程通過調(diào)用select或poll或epoll去查詢多個(gè)文件描述符的狀態(tài)脊奋。
如果有一個(gè)文件描述符準(zhǔn)備好了,內(nèi)核就返回該文件描述符可讀的消息疙描。
用戶進(jìn)程調(diào)用一個(gè)recvfrom請求诚隙,內(nèi)核收到請求后開始拷貝數(shù)據(jù),將數(shù)據(jù)從內(nèi)核緩存區(qū)拷貝到用戶進(jìn)程緩存區(qū)起胰,拷貝完成后返回給進(jìn)程ok的消息久又。
上面整個(gè)過程都是阻塞的。
- signal driven IO(信號(hào)驅(qū)動(dòng)IO)
調(diào)用過程:
設(shè)置socket為一個(gè)信號(hào)驅(qū)動(dòng)IO效五,并且通過sigaction system call安裝一個(gè)signal handler地消。
數(shù)據(jù)準(zhǔn)備好以后,一個(gè)SIGIO信號(hào)傳送給用戶進(jìn)程通知數(shù)據(jù)已經(jīng)準(zhǔn)備好畏妖。
然后用戶進(jìn)程調(diào)用一個(gè)recvfrom請求脉执,內(nèi)核收到請求后開始拷貝數(shù)據(jù),將數(shù)據(jù)從內(nèi)核緩存區(qū)拷貝到用戶進(jìn)程緩存區(qū)戒劫,拷貝完成后返回給進(jìn)程ok的消息半夷。
步驟1因?yàn)槭撬矔r(shí)完成的,可以認(rèn)為是非阻塞的迅细。步驟2巫橄,3是阻塞的。
- asynchronous IO(異步IO)
調(diào)用過程:
用戶進(jìn)程發(fā)送一個(gè)aio_read的系統(tǒng)調(diào)用茵典,內(nèi)核立即返回響應(yīng)湘换。
用戶進(jìn)程收到響應(yīng),可以繼續(xù)做自己的事(非阻塞)敬尺。
內(nèi)核等待數(shù)據(jù)到達(dá)枚尼,完成數(shù)據(jù)拷貝后,發(fā)送消息給用戶進(jìn)程砂吞。
上面整個(gè)過程都是非阻塞的。
三 windowsIO模型
windwos IO模型感覺和linuxIO模型相似崎溃,感興趣的同學(xué)可以看看這個(gè)
ps: 本文的圖和英文引用部分來自參考文檔3.中文引用部分來自文檔1(參考文檔寫的比我好蜻直,建議大家多看看 :))
參考文檔: