java 5種IO模型

每日一句

人的痛苦會(huì)把自己折磨到多深呢?

每日一句

You cannot swim for new horizons until you have courage to lose sight of the shore.
除非有勇氣離開(kāi)岸邊,否則你永遠(yuǎn)游不到彼岸。

概念

IO 是主存和外部設(shè)備(硬盤(pán)、終端和網(wǎng)絡(luò)等)拷貝數(shù)據(jù)的過(guò)程。IO是操作系統(tǒng)的底層功能實(shí)現(xiàn),底層通過(guò)I/O指令進(jìn)行完成。

以下是5種類(lèi)Unix下可用的I/O模型

  1. 阻塞式I/O:Blocking IO

  2. 非阻塞式I/O:nonblocking IO

  3. I/O 復(fù)用(Select,poll epoll):IO multiplexing

  4. 信號(hào)驅(qū)動(dòng)式I/O(SIGIO):signal driven IO

  5. 異步 I/O(posix 的 aio 系列函數(shù)):asynchromous IO

Blocking IO

在 Linux 中,默認(rèn)情況下所有的 socket 都是 Blocking,一個(gè)典型的讀操作流程大概是這樣:

  1. 通常涉及等待數(shù)據(jù)從網(wǎng)絡(luò)到達(dá)。當(dāng)所有等待數(shù)據(jù)到達(dá)時(shí),它被復(fù)制到內(nèi)核中的某個(gè)緩沖區(qū)

  2. 把數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到應(yīng)用程序緩沖區(qū)

當(dāng)用戶進(jìn)程調(diào)用了 recvfrom 這個(gè)系統(tǒng)調(diào)用, kernel 就開(kāi)始了 IO 的第一個(gè)階段:準(zhǔn)備數(shù)據(jù)。對(duì)于 network IO 來(lái)說(shuō),很多時(shí)候數(shù)據(jù)在一開(kāi)始還沒(méi)有到達(dá)(比如,還沒(méi)有收到一個(gè)完整的 UDP 包)。這個(gè)時(shí)候 kernel 就要等待足夠的數(shù)據(jù)到來(lái)。而在用戶進(jìn)程這邊,整個(gè)進(jìn)程會(huì)被阻塞。當(dāng) kernel 一直等到數(shù)據(jù)準(zhǔn)備好了,它就會(huì)將數(shù)據(jù)從kernel 中拷貝到用戶內(nèi)存,然后kernel返回結(jié)果,用戶進(jìn)程才解除 block 的狀態(tài),重新運(yùn)行起來(lái)。
所以,Blocking IO 的特點(diǎn)就是在IO執(zhí)行的兩個(gè)階段都被 block了

非阻塞式I/O

Linux 下,可以用過(guò)設(shè)置 socket 使其變?yōu)?non-blocking。當(dāng)對(duì)一個(gè) non-blocking socket 執(zhí)行操作時(shí),流程如下:
從圖中可以看出,當(dāng)用戶進(jìn)程發(fā)出read操作時(shí),如果kernel中的數(shù)據(jù)還沒(méi)有準(zhǔn)備好,那么它并不會(huì)block用戶進(jìn)程,而是立刻返回一個(gè)error。 從用戶進(jìn)程角度講 ,它發(fā)起一個(gè)read操作后,并不需要等待,而是馬上就得到了一個(gè)結(jié)果。用戶進(jìn)程判斷結(jié)果是一個(gè)error時(shí),它就知道數(shù)據(jù)還沒(méi)有準(zhǔn)備好,于是它可以再次 發(fā)送read操作。一旦kernel中的數(shù)據(jù)準(zhǔn)備好了,并且又再次收到了用戶進(jìn)程的system call,那么它馬上就將數(shù)據(jù)拷貝到了用戶內(nèi)存,然后返回。

所以,用戶進(jìn)程第一個(gè)階段不是阻塞的,需要不斷的主動(dòng)詢問(wèn)kernel數(shù)據(jù)好了沒(méi)有;第二個(gè)階段依然總是阻塞的。

IO 多路復(fù)用(NIO)

select、epoll 的好處就在于單個(gè) process 就可以同時(shí)處理多個(gè)網(wǎng)絡(luò)鏈接的 IO。

IO 復(fù)用和同步阻塞本質(zhì)一樣,不過(guò)利用了新的 Select 系統(tǒng)調(diào)用,由內(nèi)核來(lái)負(fù)責(zé)本來(lái)是請(qǐng)求進(jìn)程該做的輪訓(xùn)操作,看似不非阻塞IO還多了哥系統(tǒng)調(diào)用開(kāi)銷(xiāo),不過(guò)因?yàn)橹С侄嗦稩O才算提高了效率
也就是一個(gè)可以監(jiān)聽(tīng)多個(gè)。
它的基本原理就是 select、epoll 這個(gè) function會(huì)不斷的輪詢所負(fù)責(zé)的所有 socket,當(dāng)某個(gè) socket 有數(shù)據(jù)到達(dá)了,就通知用戶進(jìn)程。它的流程如下圖:

當(dāng)用戶線程調(diào)用select,那么整個(gè)進(jìn)程會(huì)被阻塞,而同時(shí),kernel會(huì)監(jiān)視所有select負(fù)責(zé)的 socket =,當(dāng)任何一個(gè) socket 中的數(shù)據(jù)準(zhǔn)備好了,select 就會(huì)返回,這個(gè)時(shí)候用戶進(jìn)程會(huì)調(diào)用 read 操作,將數(shù)據(jù) kernel 拷貝到用戶進(jìn)程。
首先開(kāi)啟套接字的信號(hào)驅(qū)動(dòng)式IO功能,并且通過(guò)sigaction(信號(hào)處理程序) 系統(tǒng)調(diào)用安裝一個(gè)信號(hào)處理函數(shù) ,該函數(shù)調(diào)用將立即返回,當(dāng)前進(jìn)程沒(méi)有被阻塞 ,繼續(xù)工作;當(dāng)數(shù)據(jù)報(bào)準(zhǔn)備好的時(shí)候,內(nèi)核為該進(jìn)程產(chǎn)生SIGIO 的信號(hào),隨后既可以在信號(hào)處理函數(shù)中調(diào)用recvfrom 讀取數(shù)據(jù)報(bào),并且通知主循環(huán)數(shù)據(jù)已經(jīng)準(zhǔn)備好等待處理;也可以直接通知主循環(huán)讓它讀取數(shù)據(jù)報(bào);(其實(shí)就是一個(gè)待讀取的通知和待處理的通知),基本不會(huì)用到。

異步IO(AIO)

多線程和多進(jìn)程的模型雖然解決了并發(fā)的問(wèn)題,但是系統(tǒng)不能無(wú)限的增加線程,由于系統(tǒng)的切換線程的開(kāi)銷(xiāo)恒大,所以,一旦線程數(shù)量過(guò)多,CPU的時(shí)間就花在線程的切換上,正真運(yùn)行代碼的時(shí)間就會(huì)減少,結(jié)果導(dǎo)致性能?chē)?yán)重下降

由于我們要解決的問(wèn)題是CPU高速執(zhí)行能力和IO設(shè)備的龜速嚴(yán)重不匹配,多線程和多進(jìn)程只是解決這一個(gè)問(wèn)題的一種方法。
另一種解決IO問(wèn)題的方法是異步IO,當(dāng)代碼需要執(zhí)行一個(gè)耗時(shí)的IO操作時(shí),他只發(fā)出IO指令,并不等待IO結(jié)果然后就去執(zhí)行其他代碼,一段時(shí)間后,當(dāng)IO返回結(jié)果是,在通知CPU進(jìn)行處理我們調(diào)用aio_read函數(shù),給內(nèi)核傳遞描述符,緩沖區(qū)指針,緩沖區(qū)大小,和文件偏移量,并且告訴內(nèi)核當(dāng)整個(gè)操作完成時(shí)如何通知我們,該函數(shù)調(diào)用后,立即返回,不會(huì)被阻塞

另一方面:從kernel的角度,當(dāng)他收到一個(gè)aio_read之后,首先它立即返回,所以不會(huì)對(duì)用戶進(jìn)程產(chǎn)生block,然后kernel會(huì)等待數(shù)據(jù)準(zhǔn)備完成,然后將數(shù)據(jù)拷貝到用戶內(nèi)存(copy由內(nèi)核完成),當(dāng)著一切完成后,kernel會(huì)給用戶進(jìn)程發(fā)送一個(gè)singal或者執(zhí)行下一個(gè)基于線程回調(diào)函數(shù)來(lái)完成此次IO處理過(guò)程,告訴他read操作完成

美文佳句

歲月應(yīng)是靜好。近來(lái),心神總是不寧,想必是被塵世間的誘惑所困。

我想,要是遠(yuǎn)方的祥夫先生來(lái)到我這“梅知堂”,兩人清茶一杯,盤(pán)腿而坐,或看他畫(huà)梅,紙上的梅花仿佛兀自開(kāi)在飛雪里,如是,那該是一幅怎樣的墨梅圖?

昨晚,夢(mèng)里看到那個(gè)戴著小黑圓眼鏡的祥夫背著黃色的大包正往江南趕路。是又到了梅花開(kāi)的日子了嗎?

面試題

SpringMVC工作原理?

1. 客戶端發(fā)送請(qǐng)求到前端控制器 DispatcherServlet
2. DispatcherServlet 收到請(qǐng)求后,調(diào)用 HandlerMapping 處理器映射器,請(qǐng)求獲取 handler
3. 處理器映射器根據(jù) url 找到具體的處理器,生成處理器對(duì)象以及處理器攔截器(如果有則生成)一并返回給 DispatcherServlet
4. DispatcherServlet 調(diào)用 HandlerAdapter 處理器適配器
5. HandlerAdapter 經(jīng)過(guò)適配器調(diào)用 具體處理器(Handler,也叫后端控制器)
6. Handler 執(zhí)行完成返回 ModelAndView
7. HandlerAdaper 將 Handler 執(zhí)行結(jié)果 ModelAndView 返回給 DispatcherServlet
8. DispatcherServlet 將 ModelAndView 傳給 ViewResolver 視圖解析器 進(jìn)行解析
9. ViewResolver 解析后返回具體 View
10. DispatcherServlet 對(duì) view 進(jìn)行 渲染視圖(即將模型數(shù)據(jù)填充至視圖中)
11. DispatcherServlet 響應(yīng)用戶

ArrayList 、LinkedList和Vector的區(qū)別?

ArrayList LinkedList Vector
線程是否安全 不保證 不保證 保證線程安全,但是底層大量使用了synchronized關(guān)鍵字,效率不是很高
底層數(shù)據(jù)結(jié)構(gòu) 數(shù)組,查詢效率高,多用于查詢較多的場(chǎng)合 LinkedList 底層是雙向鏈表,插入和刪除非常方便,適用于插入較多的場(chǎng)合 數(shù)組,查詢效率高,多用于查詢較多的場(chǎng)合
默認(rèn)大小與擴(kuò)容 JDK 1.7 之前默認(rèn)大小是 10 JDK 1.7 之后是0 每次按照1.5倍擴(kuò)容 底層是鏈表結(jié)構(gòu),是不連續(xù)的存儲(chǔ)空間,沒(méi)有默認(rèn)大小的說(shuō)法 Vector擴(kuò)容是2倍

@RestController 和 @Controller 有什么區(qū)別?

@RestController 注解,在 @Controller 基礎(chǔ)上,增加了 @ResponseBody 注解,更加適合目前前后端分離的架構(gòu)下,提供 Restful API ,返回例如 JSON 數(shù)據(jù)格式。當(dāng)然,返回什么樣的數(shù)據(jù)格式,根據(jù)客戶端的 "ACCEPT" 請(qǐng)求頭來(lái)決定。

你好,我是yltrcc,日常分享技術(shù)點(diǎn)滴,歡迎關(guān)注我:ylcoder

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容