JAVA IO
Java IO 即 Java 輸入輸出。在開發(fā)應(yīng)用軟件時(shí)议惰,很多時(shí)候都需要和各種輸入輸出相關(guān)的媒介打交道揽咕。與媒介進(jìn)行 IO 操作的過程十分復(fù)雜,需要考慮眾多因素粟关,比如:進(jìn)行 IO 操作媒介的類型(文件疮胖、控制臺(tái)、網(wǎng)絡(luò))闷板、通信方式(順序澎灸、隨機(jī)、二進(jìn)制遮晚、按字符性昭、按字、按行等等)县遣。
Java 類庫提供了相應(yīng)的類來解決這些難題糜颠,這些類就位于 java.io 包中, 在整個(gè) java.io 包中最重要的就是 5 個(gè)類和一個(gè)接口萧求。5 個(gè)類指的是 File其兴、OutputStream、InputStream夸政、Writer元旬、Reader;一個(gè)接口指的是 Serializable秒梳。
由于老的 Java IO 標(biāo)準(zhǔn)類提供 IO 操作(如 read()法绵,write())都是同步阻塞的,因此酪碘,IO 通常也被稱為阻塞 IO(即 BIO朋譬,Blocking I/O)。
JAVA-NIO
在 JDK1.4 之后兴垦,為了提高 Java IO 的效率徙赢,Java 又提供了一套 New IO(NIO)字柠,原因在于它相對(duì)于之前的 IO 類庫是新增的。此外狡赐,舊的 IO 類庫提供的 IO 方法是阻塞的窑业,New IO 類庫則讓 Java 可支持非阻塞 IO,所以枕屉,更多的人喜歡稱之為非阻塞 IO(Non-blocking IO)常柄。
*注意:異步只有異步,同步才有阻塞和非阻塞的說法搀擂!
IO模型
- 阻塞式IO(blocking IO)西潘,即傳統(tǒng)的IO模型
- 非阻塞式IO( non-blocking IO),默認(rèn)創(chuàng)建的socket都是阻塞的
- IO多路復(fù)用(IO multiplexing) 哨颂,即經(jīng)典的Reactor設(shè)計(jì)模式喷市,有時(shí)也稱為異步阻塞IO
- 異步IO(asynchronous IO),即經(jīng)典的Proactor設(shè)計(jì)模式威恼,也稱為異步非阻塞IO
- 信號(hào)驅(qū)動(dòng)式IO(signal driven IO)
IO操作品姓,主要分為兩部分
- 數(shù)據(jù)準(zhǔn)備,將數(shù)據(jù)加載到內(nèi)核緩存(數(shù)據(jù)加載到操作系統(tǒng))
- 將內(nèi)核緩存中的數(shù)據(jù)加載到用戶緩存(從操作系統(tǒng)復(fù)制到應(yīng)用中)
同步和異步的概念描述的是用戶線程與內(nèi)核的交互方式箫措。
阻塞和非阻塞的概念描述的是用戶線程調(diào)用內(nèi)核IO操作的方式腹备。
同步阻塞IO
同步阻塞IO模型是最簡(jiǎn)單的IO模型,用戶線程在內(nèi)核進(jìn)行IO操作時(shí)被阻塞
用戶線程通過系統(tǒng)調(diào)用read發(fā)起IO讀操作蒂破,由用戶控件轉(zhuǎn)到內(nèi)核空間馏谨。
內(nèi)核等到數(shù)據(jù)包到達(dá)后别渔,然后將接收的數(shù)據(jù)拷貝到用戶空間附迷,完成read操作。
同步非阻塞IO
同步阻塞IO是在同步阻塞IO的基礎(chǔ)上哎媚,將socket設(shè)置為NIO(NOBLOCK)喇伯。
這樣做用戶線程可以在發(fā)起IO請(qǐng)求后可以立即返回。
由于socket是非阻塞的方式拨与,因此用戶線程發(fā)起IO請(qǐng)求時(shí)立即返回稻据。
但未讀取到任何數(shù)據(jù),用戶線程需要不斷地發(fā)起IO請(qǐng)求买喧,直到數(shù)據(jù)到達(dá)后捻悯,才真正讀取數(shù)據(jù),繼續(xù)執(zhí)行淤毛。
IO多路復(fù)用
IO多路復(fù)用模型是建立在內(nèi)核提供的多路分離函數(shù)select基礎(chǔ)之上的今缚。
使用select函數(shù)可以避免同步非阻塞IO模型中輪詢等待的問題。
用戶首先將需要進(jìn)行IO操作的socket添加到select中低淡,然后阻塞等待select系統(tǒng)調(diào)用返回姓言。
當(dāng)數(shù)據(jù)到達(dá)時(shí)瞬项,socket被激活,select函數(shù)返回何荚。用戶線程正式發(fā)起read請(qǐng)求囱淋,讀取數(shù)據(jù)并繼續(xù)執(zhí)行。
從流程上來看餐塘,使用select函數(shù)進(jìn)行IO請(qǐng)求和同步阻塞模型沒有太大的區(qū)別妥衣,
甚至還多了添加監(jiān)視socket,以及調(diào)用select函數(shù)的額外操作戒傻,效率更差称鳞。
但是,使用select以后最大的優(yōu)勢(shì)是用戶可以在一個(gè)線程內(nèi)同時(shí)處理多個(gè)socket的IO請(qǐng)求稠鼻。
用戶可以注冊(cè)多個(gè)socket冈止,然后不斷地調(diào)用select讀取被激活的socket,即可達(dá)到在同一個(gè)線程內(nèi)同時(shí)處理多個(gè)IO請(qǐng)求的目的候齿。
而在同步阻塞模型中熙暴,必須通過多線程的方式才能達(dá)到這個(gè)目的。
然而慌盯,使用select函數(shù)的優(yōu)點(diǎn)并不僅限于此周霉。
雖然上述方式允許單線程內(nèi)處理多個(gè)IO請(qǐng)求,但是每個(gè)IO請(qǐng)求的過程還是阻塞的(在select函數(shù)上阻塞)亚皂,平均時(shí)間甚至比同步阻塞IO模型還要長(zhǎng)俱箱。
如果用戶線程只注冊(cè)自己感興趣的socket或者IO請(qǐng)求,然后去做自己的事情灭必,等到數(shù)據(jù)到來時(shí)再進(jìn)行處理狞谱,則可以提高CPU的利用率。
IO多路復(fù)用模型使用了Reactor設(shè)計(jì)模式實(shí)現(xiàn)了這一機(jī)制禁漓。
首先跟衅,要從你常用的IO操作談起,比如read和write播歼,通常IO操作都是阻塞I/O的伶跷,也就是說當(dāng)你調(diào)用read時(shí),
如果沒有數(shù)據(jù)收到秘狞,那么線程或者進(jìn)程就會(huì)被掛起叭莫,直到收到數(shù)據(jù)。
這樣烁试,當(dāng)服務(wù)器需要處理1000個(gè)連接的的時(shí)候雇初,而且只有很少連接忙碌的,
那么會(huì)需要1000個(gè)線程或進(jìn)程來處理1000個(gè)連接廓潜,而1000個(gè)線程大部分是被阻塞起來的抵皱。
由于CPU的核數(shù)或超線程數(shù)一般都不大善榛,比如4,8,16,32,64,128,比如4個(gè)核要跑1000個(gè)線程呻畸,那么每個(gè)線程的時(shí)間槽非常短移盆,而線程切換非常頻繁。
這樣是有問題的:
1伤为,線程是有內(nèi)存開銷的咒循,1個(gè)線程可能需要512K(或2M)存放棧,那么1000個(gè)線程就要512M(或2G)內(nèi)存绞愚。
2叙甸,線程的切換,或者說上下文切換是有CPU開銷的位衩,當(dāng)大量時(shí)間花在上下文切換的時(shí)候裆蒸,分配給真正的操作的CPU就要少很多。
那么糖驴,我們就要引入非阻塞I/O的概念僚祷,非阻塞IO很簡(jiǎn)單,通過fcntl(POSIX)或ioctl(Unix)設(shè)為非阻塞模式贮缕,
這時(shí)辙谜,當(dāng)你調(diào)用read時(shí),如果有數(shù)據(jù)收到感昼,就返回?cái)?shù)據(jù)装哆,如果沒有數(shù)據(jù)收到,就立刻返回一個(gè)錯(cuò)誤定嗓,如EWOULDBLOCK蜕琴。
這樣是不會(huì)阻塞線程了,但是你還是要不斷的輪詢來讀取或?qū)懭搿?/p>
于是蜕乡,我們需要引入IO多路復(fù)用的概念奸绷。
多路復(fù)用是指使用一個(gè)線程來檢查多個(gè)文件描述符(Socket)的就緒狀態(tài)梗夸,比如調(diào)用select和poll函數(shù)层玲,傳入多個(gè)文件描述符,
如果有一個(gè)文件描述符就緒反症,則返回辛块,否則阻塞直到超時(shí)。
得到就緒狀態(tài)后進(jìn)行真正的操作可以在同一個(gè)線程里執(zhí)行铅碍,也可以啟動(dòng)線程執(zhí)行(比如使用線程池)润绵。
這樣在處理1000個(gè)連接時(shí),只需要1個(gè)線程監(jiān)控就緒狀態(tài)胞谈,對(duì)就緒的每個(gè)連接開一個(gè)線程處理就可以了尘盼,
這樣需要的線程數(shù)大大減少憨愉,減少了內(nèi)存開銷和上下文切換的CPU開銷。
異步IO
“真正”的異步IO需要操作系統(tǒng)更強(qiáng)的支持卿捎。
在IO多路復(fù)用模型中配紫,事件循環(huán)將文件句柄的狀態(tài)事件通知給用戶線程,由用戶線程自行讀取數(shù)據(jù)午阵、處理數(shù)據(jù)躺孝。
而在異步IO模型中,當(dāng)用戶線程收到通知時(shí)底桂,數(shù)據(jù)已經(jīng)被內(nèi)核讀取完畢植袍,并放在了用戶線程指定的緩沖區(qū)內(nèi),內(nèi)核在IO完成后通知用戶線程直接使用即可籽懦。
異步IO模型使用了Proactor設(shè)計(jì)模式實(shí)現(xiàn)了這一機(jī)制于个。
相比于IO多路復(fù)用模型,異步IO并不十分常用暮顺,不少高性能并發(fā)服務(wù)程序使用IO多路復(fù)用模型+多線程任務(wù)處理的架構(gòu)基本可以滿足需求嘹锁。
況且目前操作系統(tǒng)對(duì)異步IO的支持并非特別完善,更多的是采用IO多路復(fù)用模型模擬異步IO的方式
(IO事件觸發(fā)時(shí)不直接通知用戶線程七咧,而是將數(shù)據(jù)讀寫完畢后放到用戶指定的緩沖區(qū)中)踏施。
Java7之后已經(jīng)支持了異步IO,感興趣的讀者可以嘗試使用宙项。