Java IO

一、傳統(tǒng)的BIO

1.網(wǎng)絡(luò)編程的基本模型是Client/Server模型惶翻,也就是兩個(gè)進(jìn)程之間進(jìn)行相互通信蝶溶,其中服務(wù)端提供位置信息(綁定的IP地址和監(jiān)聽端口),客戶端通過連接操作向服務(wù)端監(jiān)聽的地址發(fā)起連接請(qǐng)求俱济,通過三次握手建立連接,如果連接建立成功钙勃,雙方就可以通過網(wǎng)絡(luò)套接字(Socket)進(jìn)行通信蛛碌。在基于傳統(tǒng)同步阻塞模型開發(fā)中,ServerSocket負(fù)責(zé)綁定IP地址辖源,啟動(dòng)監(jiān)聽端口蔚携;Socket負(fù)責(zé)發(fā)起連接操作。連接成功之后克饶,雙方通過輸入和輸出流進(jìn)行同步阻塞式通信酝蜒。

該模型最大的問題就是缺乏彈性伸縮能力,當(dāng)客戶端并發(fā)訪問量增加后矾湃,服務(wù)端的線程個(gè)數(shù)和客戶端并發(fā)訪問數(shù)呈1:1 的正比關(guān)系亡脑,猶豫線程是Java虛擬機(jī)非常寶貴的系統(tǒng)資源,當(dāng)線程數(shù)膨脹之后邀跃,系統(tǒng)的性能將急劇下降霉咨,隨著并發(fā)訪問量的繼續(xù)增大,系統(tǒng)會(huì)發(fā)生線程堆棧溢出拍屑、創(chuàng)建新線程失敗等問題途戒,并最終導(dǎo)致進(jìn)程宕機(jī)或者僵死,不能對(duì)外提供服務(wù)丽涩。

我們發(fā)現(xiàn)棺滞,BIO主要的問題在于每當(dāng)有一個(gè)新的客戶端請(qǐng)求接入時(shí),服務(wù)端必須創(chuàng)建一個(gè)新的線程處理新接入的客戶端鏈路矢渊,一個(gè)線程只能吃力一個(gè)客戶端連接继准。在高性能服務(wù)器應(yīng)用領(lǐng)域,往往需要面向成千上萬(wàn)個(gè)客戶端的并發(fā)連接矮男,這種模型顯然無(wú)法滿足高性能移必、高并發(fā)接入的場(chǎng)景。

為了改進(jìn)一線程一連接模型毡鉴,后來又演進(jìn)出了一種通過線程池或者消息隊(duì)列實(shí)現(xiàn)1個(gè)或者多個(gè)線程處理N個(gè)客戶端的模型崔泵,由于它的底層通信機(jī)制依然使用同步阻塞I/O秒赤,所以被稱為“偽異步”。后面我們將通過對(duì)偽異步代碼的分析憎瘸,看看偽異步能否滿足我們對(duì)高性能入篮、高并發(fā)接入的訴求。

二幌甘、偽異步IO編程

1.采用線程池和任務(wù)隊(duì)列可以實(shí)現(xiàn)一種叫做偽異步的I/O通信框架潮售,它的模型圖如下所示。

當(dāng)有新的客戶端接入的時(shí)候锅风,將客戶端的Socket封裝成一個(gè)Task(該任務(wù)實(shí)現(xiàn)java.lang.Runnable接口)投遞到后端的線程池中進(jìn)行處理酥诽,JDK的線程池維護(hù)一個(gè)消息隊(duì)列和N個(gè)活躍線程對(duì)消息隊(duì)列中的任務(wù)進(jìn)行處理。由于線程池可以設(shè)置消息隊(duì)列的大小和最大線程數(shù)皱埠。因此肮帐,它的資源占用是可控的,無(wú)論多少個(gè)客戶端并發(fā)訪問边器,都不會(huì)導(dǎo)致資源的耗盡和宕機(jī)训枢。

學(xué)習(xí)過TCP/IP相關(guān)知識(shí)的人都知道,當(dāng)消息的接收方處理緩慢的時(shí)候忘巧,將不能及時(shí)地從TCP緩沖區(qū)讀取數(shù)據(jù)肮砾,這將會(huì)導(dǎo)致發(fā)送方的TCP window size不斷減小,直到為0袋坑,雙方處于Keep-Alive狀態(tài),消息發(fā)送方將不能再向TCP緩沖區(qū)寫入消息眯勾,這是如果采用的是同步阻塞I/O枣宫,write操作將會(huì)被無(wú)限期阻塞,直到TCP window size大于0或者發(fā)生I/O異常吃环。

?  通過對(duì)輸入和輸出流的API文檔進(jìn)行分析也颤,我們了解到讀和寫操作都是同步阻塞的,阻塞的時(shí)間取決于對(duì)方I/O線程的處理速度和網(wǎng)絡(luò)I/O傳輸速度郁轻。本質(zhì)上來講翅娶,我們無(wú)法保證生產(chǎn)環(huán)境的網(wǎng)絡(luò)狀況和對(duì)端的應(yīng)用程序能夠足夠快,如果我們的應(yīng)用程序依賴對(duì)方的處理速度好唯,它的可靠性就非常差竭沫。

  偽異步I/O實(shí)際上僅僅只是對(duì)之前I/O線程模型的一個(gè)簡(jiǎn)單優(yōu)化,它無(wú)法從根本上解決同步I/O導(dǎo)致的通信線程阻塞問題骑篙。下面我們就簡(jiǎn)單分析下如果通信對(duì)方返回應(yīng)答時(shí)間過長(zhǎng)蜕提,會(huì)引起的級(jí)聯(lián)故障。

 服務(wù)端處理緩慢靶端,返回應(yīng)答消息耗費(fèi)60s谎势,平時(shí)只需要10ms凛膏。

? ?采用偽異步I/O的線程正在讀取故障服務(wù)節(jié)點(diǎn)的響應(yīng),由于讀取輸入流是阻塞的脏榆,因此猖毫,它將會(huì)被同步阻塞60s。

? ?假如所有的可用線程都被故障服務(wù)器阻塞须喂,那后續(xù)所有的I/O消息都將在隊(duì)里中排隊(duì)吁断。

? ?由于線程池采用阻塞隊(duì)里實(shí)現(xiàn),當(dāng)隊(duì)列積滿之后镊折,后續(xù)入隊(duì)的操作將被阻塞胯府。

? ?由于前端只有一個(gè)Accptor線程接收客戶端接入,它被阻塞在線程池的同步阻塞隊(duì)列之后恨胚,新的客戶端請(qǐng)求消息將被拒絕骂因, ? ? ?客戶端會(huì)發(fā)生大量的連接超時(shí)。

? ?由于幾乎所有的連接都超時(shí)赃泡,調(diào)用者會(huì)認(rèn)為系統(tǒng)已經(jīng)崩潰寒波,無(wú)法接收新的請(qǐng)求消息。

三升熊、NIO

新的輸入/輸出(NIO)庫(kù)是在JDK1.4中引入的俄烁。NIO彌補(bǔ)了原來同步阻塞I/O的不足,它在標(biāo)準(zhǔn)Java代碼中提供了高速的级野、面向塊的I/O页屠。通過定義包含數(shù)據(jù)的類,以及通過以塊的形式處理這些數(shù)據(jù)蓖柔,NIO不使用本機(jī)代碼就可以利用低級(jí)優(yōu)化辰企,這是原來的I/O包所無(wú)法做到的。下面對(duì)NIO的一些概念和功能做下簡(jiǎn)單介紹况鸣,以便大家能夠快速地了解NIO類庫(kù)和相關(guān)概念牢贸。

1.緩沖區(qū)Buffer

  Buffer是一個(gè)對(duì)象,它包含一些要寫入或者要讀出的數(shù)據(jù)镐捧。在NIO類庫(kù)中加入Buffer對(duì)象潜索,體現(xiàn)了新庫(kù)與原I/O的一個(gè)重要區(qū)別。在面向流的I/O中懂酱,可以將數(shù)據(jù)直接寫入或者將數(shù)據(jù)直接讀到Stream對(duì)象中竹习。

  在NIO庫(kù)中,所有數(shù)據(jù)都是用緩沖區(qū)處理的列牺。在讀取數(shù)據(jù)時(shí)由驹,它是直接讀到緩沖區(qū)中的;在寫入數(shù)據(jù)時(shí),寫入到緩沖區(qū)中蔓榄。任何時(shí)候訪問NIO中的數(shù)據(jù)并炮,都是通過緩沖區(qū)進(jìn)行操作。

  緩沖區(qū)實(shí)質(zhì)上是一個(gè)數(shù)組甥郑。通常它是一個(gè)字節(jié)數(shù)組(ByteBuffer)逃魄,也可以使用其他種類的數(shù)組。但是緩沖區(qū)不僅僅是一個(gè)數(shù)組澜搅,緩沖區(qū)提供了對(duì)數(shù)據(jù)的結(jié)構(gòu)化訪問以及維護(hù)讀寫位置(limit)等信息伍俘。

  最常用的緩沖區(qū)是ByteBuffer,一個(gè)ByteBuffer提供了一組功能用于操作byte數(shù)組勉躺。除了ByteBuffer癌瘾,還有其他的一些緩沖區(qū),事實(shí)上饵溅,每一種Java基本類型(除了Boolean類型)都對(duì)應(yīng)有一種緩沖區(qū)妨退,具體如下:

ByteBuffer:字節(jié)緩沖區(qū)

CharBuffer:字符緩沖區(qū)

ShortBuffer:短整型緩沖區(qū)

IntBuffer:整型緩沖區(qū)

LongBuffer:長(zhǎng)整型緩沖區(qū)

FloatBuffer:浮點(diǎn)型緩沖區(qū)

DoubleBuffer:雙精度浮點(diǎn)型緩沖區(qū)

  ?每一個(gè)Buffer類都是Buffer接口的一個(gè)子實(shí)例。除了ByteBuffer,每一個(gè)Buffer類都有完全一樣的操作蜕企,只是它們所處理的數(shù)據(jù)類型不一樣咬荷。因?yàn)榇蠖鄶?shù)標(biāo)準(zhǔn)I/O操作都是使用ByteBuffer,所以它除了具有一般緩沖區(qū)的操作之外還提供一些特有的操作轻掩,方便網(wǎng)絡(luò)讀寫幸乒。

2.通道Channel

  Channel是一個(gè)通道,可以通過它讀取和寫入數(shù)據(jù)唇牧,它就像自來水管一樣罕扎,網(wǎng)絡(luò)數(shù)據(jù)通過Channel讀取和寫入。通道與流的不同之處在于通道是雙向的丐重,流只是在一個(gè)方向上移動(dòng)(一個(gè)流必須是InputStream或者OutputStream的子類)壳影,而且通道可以用于讀、寫或者同時(shí)讀寫弥臼。因?yàn)镃hannel是全雙工的,所以它可以比流更好地映射底層操作系統(tǒng)的API根灯。

3.多路復(fù)用器Selector

  多路復(fù)用器Selector是Java NIO編程的基礎(chǔ)径缅,熟練地掌握Selector對(duì)于掌握NIO編程至關(guān)重要。多路復(fù)用器提供選擇已經(jīng)就緒的任務(wù)的能力烙肺。簡(jiǎn)單來講纳猪,Selector會(huì)不斷地輪詢注冊(cè)在其上的Channel,如果某個(gè)Channel上面有新的TCP連接接入桃笙、讀和寫事件氏堤,這個(gè)Channel就處于就緒狀態(tài),會(huì)被Selector輪詢出來,然后通過SelectionKey可以獲取就緒Channel的集合鼠锈,進(jìn)行后續(xù)的I/O操作闪檬。

  一個(gè)多路復(fù)用器Selector可以同時(shí)輪詢多個(gè)Channel,由于JDK使用了epoll()代替?zhèn)鹘y(tǒng)的select實(shí)現(xiàn)购笆,所以它并沒有最大連接句柄1024/2048的限制粗悯。這也就意味著只需要一個(gè)線程負(fù)責(zé)Selector的輪詢,就可以接入成千上萬(wàn)的客戶端同欠,這確實(shí)是個(gè)非常巨大的進(jìn)步样傍。

2.NIO服務(wù)端序列圖

  NIO服務(wù)端通信序列圖如下圖所示:

3.NIO客戶端序列圖

  NIO客戶端創(chuàng)建序列圖如圖所示。

 通過源碼對(duì)比分析發(fā)現(xiàn)铺遂,NIO編程難度確實(shí)比同步阻塞BIO大很多衫哥,此處我們的NIO例程并沒有考慮“半包讀”和“半包寫”,如果加上這些襟锐,代碼會(huì)更加復(fù)雜撤逢。NIO代碼既然這么復(fù)雜,為什么它的應(yīng)用卻越來越廣泛呢捌斧,使用NIO編程的優(yōu)點(diǎn)總結(jié)如下:

客戶端發(fā)起的連接操作是異步的笛质,可以通過多路復(fù)用器注冊(cè)O(shè)P_CONNECT等待后續(xù)結(jié)果,不需要像之前的客戶端那樣被同步阻塞捞蚂。

SocketChannel的讀寫操作都是異步的妇押,如果沒有可讀寫的數(shù)據(jù)它不會(huì)同步等待,直接返回姓迅,這樣I/O通信線程就可以處理其他的鏈路敲霍,不需要同步等待這個(gè)鏈路可用。

線程模型的優(yōu)化:由于JDK的Selector在Linux等主流操作系統(tǒng)上通過epoll實(shí)現(xiàn)丁存,它沒有連接句柄數(shù)的限制(只受限于操作系統(tǒng)的最大句柄數(shù)或者對(duì)單個(gè)進(jìn)程的句柄限制)肩杈,這意味著一個(gè)Selector線程可以同時(shí)處理成千上萬(wàn)個(gè)客戶端連接,而且性能不會(huì)隨著客戶端的增加而線性下降解寝,因此扩然,它非常適合做高性能、高負(fù)載的網(wǎng)絡(luò)服務(wù)器聋伦。

  JDK1.7升級(jí)了NIO類庫(kù)夫偶,升級(jí)后的NIO類庫(kù)被稱為NIO 2.0。引入注目的是觉增,Java正式提供了異步文件I/O操作兵拢,同時(shí)提供了與UNIX網(wǎng)絡(luò)編程事件驅(qū)動(dòng)I/O對(duì)應(yīng)的AIO。

四逾礁、AIO

NIO2.0引入了新的異步通道的概念说铃,并提供了異步文件通道和異步套接字通道的實(shí)現(xiàn)。異步通道提供兩種方式獲取操作結(jié)果。

通過java.util.concurrent.Future類來表示異步操作的結(jié)果腻扇;

在執(zhí)行異步操作的時(shí)候傳入一個(gè)java.nio.channels债热。

CompletionHandler接口的實(shí)現(xiàn)類作為操作完成的回調(diào)。

NIO2.0的異步套接字通道是真正的異步非阻塞I/O衙解,它對(duì)UNIX網(wǎng)絡(luò)編程中的事件驅(qū)動(dòng)I/O(AIO)阳柔,它不需要通過多路復(fù)用器(Selector)對(duì)注冊(cè)的通道進(jìn)行輪詢操作即可實(shí)現(xiàn)異步讀寫,從而簡(jiǎn)化了NIO的編程模型蚓峦。

異步SocketChannel是被動(dòng)執(zhí)行對(duì)象舌剂,我們不需要像NIO編程那樣創(chuàng)建一個(gè)獨(dú)立I/O線程來處理讀寫操作。對(duì)于AsynchronousServerSocketChannel和?AsynchronousSocketChannel暑椰,它們都由JDK底層的線程池負(fù)責(zé)回調(diào)并驅(qū)動(dòng)讀寫操作霍转。正因?yàn)槿绱耍贜IO2.0新的異步非阻塞Channel進(jìn)行編程比NIO編程更為簡(jiǎn)單一汽。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末避消,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子召夹,更是在濱河造成了極大的恐慌岩喷,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件监憎,死亡現(xiàn)場(chǎng)離奇詭異纱意,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)鲸阔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門偷霉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人褐筛,你說我怎么就攤上這事类少。” “怎么了渔扎?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵硫狞,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我晃痴,道長(zhǎng)残吩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任愧旦,我火速辦了婚禮,結(jié)果婚禮上定罢,老公的妹妹穿的比我還像新娘笤虫。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布琼蚯。 她就那樣靜靜地躺著酬凳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪遭庶。 梳的紋絲不亂的頭發(fā)上宁仔,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音峦睡,去河邊找鬼翎苫。 笑死,一個(gè)胖子當(dāng)著我的面吹牛榨了,可吹牛的內(nèi)容都是我干的煎谍。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼龙屉,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼呐粘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起转捕,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤作岖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后五芝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體痘儡,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年与柑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谤辜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡价捧,死狀恐怖丑念,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情结蟋,我是刑警寧澤脯倚,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站嵌屎,受9級(jí)特大地震影響推正,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜宝惰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一植榕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧尼夺,春花似錦尊残、人聲如沸炒瘸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)顷扩。三九已至,卻和暖如春慰毅,著一層夾襖步出監(jiān)牢的瞬間隘截,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工汹胃, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留婶芭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓统台,卻偏偏與公主長(zhǎng)得像雕擂,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子贱勃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容