Java IO對(duì)大多數(shù)Java程序員來(lái)說(shuō)是熟悉又陌生,熟悉的是感覺(jué)到處都有它的身影,小到簡(jiǎn)單的讀取文件,大到各種服務(wù)器的應(yīng)用,陌生的是Java IO背后到底是一個(gè)怎樣的機(jī)制,今天就讓我們?nèi)チ私庖幌逻@位老朋友吧。本文不講解Java IO如何具體使用,有這方面需求的同學(xué)可以自己查下。
IO模型要說(shuō)IO,就不得不說(shuō)IO模型,IO模型大家都有所了解,同步異步,阻塞非阻塞什么的,總的來(lái)說(shuō)IO模型可分為以下五種:
阻塞IO
非阻塞IO
多路復(fù)用IO
信號(hào)驅(qū)動(dòng)IO
異步IO
那么這幾種IO都有什么區(qū)別呢?下面我們一一來(lái)看,每種模型我都會(huì)舉一個(gè)適當(dāng)?shù)睦又诶斫猓?/p> 1.阻塞IO
阻塞IO相信大家都最熟悉了,線程發(fā)起一個(gè)IO請(qǐng)求,直到有結(jié)果返回,否則則一直阻塞等待,比如我們平常常見(jiàn)的阻塞數(shù)據(jù)庫(kù)操作,網(wǎng)絡(luò)IO等。
小明阻塞IO吃飯:
五年前一天周末,小明和朋友一起去商場(chǎng)的外婆家吃飯,到店后發(fā)現(xiàn)排隊(duì)的人超多,所以他就領(lǐng)了一個(gè)號(hào)碼,然后他和朋友就坐在旁邊等候,一直等著服務(wù)員叫他們的號(hào),也不能做其他事,過(guò)了一個(gè)多小時(shí)終于輪到他們了,然后他們進(jìn)店點(diǎn)菜,又得等待上菜,最后他們吃飯總共花了兩個(gè)小時(shí);
關(guān)鍵部分:
等待座位吃飯:一直阻塞,直到有座位
等待上菜:一直阻塞,直到有菜(假設(shè)菜上齊了再吃)
沒(méi)什么說(shuō)的,反正就是一直等,反應(yīng)到程序中就是一直阻塞,而一個(gè)IO請(qǐng)求需要一個(gè)線程,可想而知當(dāng)有大量的IO請(qǐng)求,線程的創(chuàng)建和銷毀,線程間的切換,線程所占用的資源等等要耗費(fèi)多少時(shí)間和資源,系統(tǒng)的性能會(huì)有多差。
2.非阻塞IO非阻塞IO和阻塞IO的最大區(qū)別就在于線程發(fā)起一個(gè)IO請(qǐng)求,不會(huì)一直堵塞直到有數(shù)據(jù),而是不斷的檢查是否已有數(shù)據(jù),若有數(shù)據(jù)則讀取數(shù)據(jù)。
小明非阻塞IO吃飯:
有了第一次的教訓(xùn),小明學(xué)乖了,他在拿到后不再傻傻的等著,而是去外婆家旁邊逛了逛,每過(guò)3分鐘他就會(huì)回來(lái),然后跑到前臺(tái)去詢問(wèn)服務(wù)員輪到他了嗎?不幸的是,排隊(duì)的人超多,直到過(guò)了半個(gè)多小時(shí)后才輪到他進(jìn)店吃飯,期間他大概問(wèn)了十幾次,他們進(jìn)店點(diǎn)菜,又得等待上菜,最后他們吃飯總共花了兩個(gè)小時(shí),基本也沒(méi)做啥其他事;
關(guān)鍵部分:
領(lǐng)號(hào)后詢問(wèn)是否輪到他:非阻塞,非詢問(wèn)期間可以做點(diǎn)別的事,但也不做了啥大事
等待上菜:一直阻塞,直到有菜(假設(shè)菜上齊了再吃)
總的來(lái)說(shuō)非阻塞IO的非阻塞主要體現(xiàn)在不需要一直等待到有數(shù)據(jù),當(dāng)然讀數(shù)據(jù)那部分操作還是阻塞的,另外這種非阻塞模式需要用戶線程自己不斷詢問(wèn)檢查,其實(shí)效率也不是太高,實(shí)際編程中運(yùn)用的也不多。
3.多路復(fù)用IO既然上面我們說(shuō)到非阻塞IO的缺點(diǎn),那么有沒(méi)有什么方式改進(jìn)呢?答案是當(dāng)然有,那就是多路復(fù)用IO,我理解的它的特點(diǎn)就是復(fù)用,首先它也是一種非阻塞IO的模型,只不過(guò)上面說(shuō)到輪詢的方式用了不同的方式處理了,當(dāng)一個(gè)線程發(fā)起IO請(qǐng)求,系統(tǒng)會(huì)將它注冊(cè)到一個(gè)多帶帶管理IO請(qǐng)求的一個(gè)線程,之后該IO的相關(guān)操作的通知狀態(tài)都有這個(gè)管理IO請(qǐng)求的線程處理,Java 1.4發(fā)布的NIO就是這種模式,我們可以大致來(lái)看一下它的流程:
// 打開(kāi)服務(wù)器套接字通道 ServerSocketChannel ssc = ServerSocketChannel.open(); // 服務(wù)器配置為非阻塞 ssc.configureBlocking(false); // 進(jìn)行服務(wù)的綁定 ssc.bind(new InetSocketAddress("localhost", 8008)); // 這里的selector就相當(dāng)于多帶帶管理IO請(qǐng)求的線程 Selector selector = Selector.open(); // 注冊(cè)到selector,等待連接 ssc.register(selector, SelectionKey.OP_ACCEPT); while (true) { selector.select(); //為IO請(qǐng)求去輪詢狀態(tài) Setkeys = selector.selectedKeys(); //多個(gè)IO請(qǐng)求的狀態(tài) Iterator keyIterator = keys.iterator(); while (keyIterator.hasNext()) { //依次處理IO請(qǐng)求 SelectionKey key = keyIterator.next(); doThing(key) ... } }
可以看出Java NIO的模式就是多路復(fù)用IO模型的應(yīng)用。
小明多路復(fù)用IO吃飯:
隨著生意越來(lái)越好,外婆家發(fā)現(xiàn)好多顧客都堵在門口等待吃飯,等待區(qū)都站不下來(lái)人了,,思來(lái)想去,外婆家準(zhǔn)備請(qǐng)一個(gè)人專門來(lái)維護(hù)顧客的排隊(duì)請(qǐng)求,這樣顧客取號(hào)后,就不用堵在門口了,我們叫他小A,小明這次取號(hào)后,將自己的相關(guān)信息告訴小A,并從小A那里獲得了一個(gè)GPS(用于小A能快速找到小明,假設(shè)有了GPS后,小A能秒速找到小明),然后小明就跟朋友們開(kāi)心的去逛商場(chǎng),看看MM,買買衣服,而小A則不斷的觀察店里的情況,當(dāng)有空座位出現(xiàn)的時(shí)候,他便會(huì)按照相關(guān)信息找到具體的顧客,將其帶回進(jìn)行用餐,但他們進(jìn)店點(diǎn)菜,還得等待上菜,最后他們吃飯總共花了兩個(gè)小時(shí),但是他們不再需要排隊(duì)等位,而是去做一些其他的事。
關(guān)鍵部分:
領(lǐng)號(hào)后委托給小A,小A觀察到有空位后帶回小明:非阻塞,領(lǐng)號(hào)后可以安心去做自己的事,不用擔(dān)心錯(cuò)過(guò)
等待上菜:一直阻塞,直到有菜(假設(shè)菜上齊了再吃)
多路復(fù)用IO可以看成普通非阻塞IO的升級(jí)版,也是目前Java編程中用到比較多的IO模型,它的優(yōu)勢(shì)在于可以處理大量的IO請(qǐng)求,用一個(gè)線程管理所有的IO請(qǐng)求,無(wú)需像阻塞IO和非阻塞IO一樣,每個(gè)IO需要一個(gè)線程處理,提升了系統(tǒng)的吞吐量。
4.信號(hào)驅(qū)動(dòng)IO信號(hào)驅(qū)動(dòng)IO相對(duì)于以上幾種模型最大的特點(diǎn)就是它支持內(nèi)核信號(hào)通知,線程在發(fā)起一個(gè)IO請(qǐng)求后,會(huì)注冊(cè)一個(gè)信號(hào)函數(shù),然后內(nèi)核在確認(rèn)數(shù)據(jù)可讀了,便會(huì)給相應(yīng)的線程發(fā)送通知,讓其進(jìn)行具體IO讀寫(xiě)操作。
小明信號(hào)驅(qū)動(dòng)IO吃飯:
又了一段時(shí)間,外婆家通過(guò)使用復(fù)用IO模式緩解了排隊(duì)擁擠的情況,但是覺(jué)得還要請(qǐng)一個(gè)人專門維護(hù)隊(duì)列,感覺(jué)不劃算,那么有沒(méi)有一種更好的方式呢?經(jīng)過(guò)一天的苦思冥想,外婆家的經(jīng)理又想出一個(gè)好辦法,讓每個(gè)顧客在領(lǐng)完號(hào)后,關(guān)注一下外婆家的公眾號(hào),然后顧客就可以去做別的事了,定時(shí)或者當(dāng)排隊(duì)信息發(fā)生改變時(shí)給顧客發(fā)送通知,告知他現(xiàn)在的排隊(duì)序號(hào)或者輪到他吃飯了,顧客可以根據(jù)相應(yīng)的信息做相應(yīng)的行為,比如快輪到了就開(kāi)始往店里走(實(shí)際程序中并不一定有這種狀態(tài),這里只是大概模擬),或者輪到自己了然后進(jìn)店吃飯,他們?nèi)匀徊挥门抨?duì)等位,而是去做一些其他的事。
關(guān)鍵部分:
領(lǐng)號(hào)后關(guān)注公眾號(hào),注冊(cè)關(guān)系:非阻塞,領(lǐng)號(hào)后可以安心去做自己的事,不用擔(dān)心錯(cuò)過(guò)
等待上菜:一直阻塞,直到有菜(假設(shè)菜上齊了再吃)
就實(shí)際來(lái)說(shuō),信號(hào)驅(qū)動(dòng)IO用的并不多,因?yàn)樾盘?hào)驅(qū)動(dòng)IO底層是使用SIGIO信號(hào),所以它主要使用在UDP協(xié)議上,因?yàn)閁DP產(chǎn)生SIGIO信號(hào)的時(shí)候只有兩種可能:
1.要么數(shù)據(jù)到達(dá)
2.發(fā)生錯(cuò)誤
但相對(duì)TCP來(lái)說(shuō),產(chǎn)生SIGIO信號(hào)的地方太多了,比如請(qǐng)求連接,確認(rèn),斷開(kāi),錯(cuò)誤等等,所以我們很難根據(jù)SIGIO信號(hào)判斷到底發(fā)生了什么。
5.異步IO以上四種IO其實(shí)都還是同步IO,因?yàn)樗鼈冊(cè)谧x寫(xiě)數(shù)據(jù)時(shí)都是阻塞的,異步IO相較于它們最大的特點(diǎn)是它讀寫(xiě)數(shù)據(jù)的時(shí)候也是非阻塞的,用戶線程在發(fā)起一個(gè)IO請(qǐng)求的時(shí)候,除了給內(nèi)核線程傳遞具體的IO請(qǐng)求外,還會(huì)給其傳遞數(shù)據(jù)緩沖區(qū),回調(diào)函數(shù)通知等內(nèi)容,然后用戶線程就繼續(xù)執(zhí)行,等到內(nèi)核線程發(fā)起相應(yīng)通知的時(shí)候,說(shuō)明數(shù)據(jù)已經(jīng)準(zhǔn)備就緒,用戶線程直接使用即可,無(wú)需再阻塞從內(nèi)核拷貝數(shù)據(jù)到用戶線程。
小明異步IO吃飯:
有過(guò)了一段時(shí)間,小明又想吃外婆家了,但是這個(gè)周末他并不想出門,他突然在網(wǎng)上看到新聞?wù)f外婆家竟然可以叫外賣,小明高興壞了,他馬上打電話給外婆家,告訴它自己想要吃哪些菜(相當(dāng)于IO請(qǐng)求所需要的數(shù)據(jù)),然后將自己的聯(lián)系號(hào)碼(相當(dāng)于回調(diào)通知)和住址(相當(dāng)于數(shù)據(jù)緩沖區(qū))也告訴它,然后就掛掉電話,開(kāi)心的做去打游戲了,過(guò)了半個(gè)小時(shí)后,手機(jī)響起,告知外賣已經(jīng)到了,小明開(kāi)門取外賣就可以直接開(kāi)吃了。整個(gè)過(guò)程小明直到吃飯都沒(méi)有等待阻塞。
關(guān)鍵部分:
叫外賣并提供相應(yīng)的信息:非阻塞,打完電話后做自己的事
通知外賣到了:直接開(kāi)門取外賣直接開(kāi)吃,非阻塞
我們可以看出,異步IO才是真正的異步,因?yàn)樗B數(shù)據(jù)拷貝這個(gè)過(guò)程都是非阻塞的,用戶線程根本不用關(guān)心數(shù)據(jù)的讀寫(xiě)等操作,只需等待內(nèi)核線程通知后,直接處理數(shù)據(jù)即可,當(dāng)然異步IO需要系統(tǒng)內(nèi)核支持,比如Linux中的AIO和Windows中的IOCP,但是也可以通過(guò)多線程跟阻塞I/O模擬異步IO,比如可以在多路復(fù)用IO模型上進(jìn)行相應(yīng)的改變,另外也有現(xiàn)有的實(shí)現(xiàn),比如異步I/O的庫(kù):libeio
最后用一張圖總體概括一下Java IO(圖片來(lái)自美團(tuán)技術(shù)博客):
Java IO概圖:
因?yàn)楹罄m(xù)會(huì)講到Java NIO,所以我們需要了解操作系統(tǒng)是如何支持多路復(fù)用IO的,Linux中支持支持三種多路IO復(fù)用機(jī)制,分別是select、poll和epoll,本來(lái)這里我想自己寫(xiě)的,但查閱了相應(yīng)的一些資料后,發(fā)現(xiàn)自己的水平還是不夠,這里我不準(zhǔn)備班門弄斧了,因?yàn)槲艺业搅撕芏鄬?xiě)的比較好的文章,這里就給大家列一下,僅供參考:
Linux系統(tǒng)編程——I/O多路復(fù)用select、poll、epoll的區(qū)別使用
聊聊IO多路復(fù)用之select、poll、epoll詳解
IO 多路復(fù)用是什么意思?
總結(jié)這篇文章主要講了最基礎(chǔ)的IO模型,不過(guò)我認(rèn)為最基礎(chǔ)的往往是最重要的,只有理解了基礎(chǔ)的原理,才能對(duì)基于它們實(shí)現(xiàn)的類庫(kù)或者工具有更加深刻的認(rèn)識(shí),下一篇文章將會(huì)主要講一下基于多路復(fù)用IO的Java NIO,敬請(qǐng)期待。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/67882.html
摘要:綁定完成后允許套接字進(jìn)行連接并等待連接。服務(wù)端根據(jù)報(bào)文返回響應(yīng),并關(guān)閉連接。單線程服務(wù)器多進(jìn)程及多線程服務(wù)器復(fù)用服務(wù)器復(fù)用的多線程服務(wù)器單線程服務(wù)器一次只處理一個(gè)請(qǐng)求,直到其完成為止。 前言 本篇文章將涉及以下內(nèi)容: IO實(shí)現(xiàn)Java Socket通信 NIO實(shí)現(xiàn)Java Socket通信 閱讀本文之前最好了解過(guò): Java IO Java NIO Java Concurrenc...
0.Why Zipkin 隨著業(yè)務(wù)發(fā)展,系統(tǒng)拆分導(dǎo)致系統(tǒng)調(diào)用鏈路愈發(fā)復(fù)雜一個(gè)前端請(qǐng)求可能最終需要調(diào)用很多次后端服務(wù)才能完成,當(dāng)整個(gè)請(qǐng)求變慢或不可用時(shí),我們是無(wú)法得知該請(qǐng)求是由某個(gè)或某些后端服務(wù)引起的,這時(shí)就需要解決如何快讀定位服務(wù)故障點(diǎn),以對(duì)癥下藥。于是就有了分布式系統(tǒng)調(diào)用跟蹤的誕生。而zipkin就是開(kāi)源分布式系統(tǒng)調(diào)用跟蹤的佼佼者 zipkin基于google-Dapper的論文有興趣的可以看下...
時(shí)間:2017年07月11日星期二說(shuō)明:本文部分內(nèi)容均來(lái)自慕課網(wǎng)。@慕課網(wǎng):http://www.imooc.com教學(xué)源碼:無(wú)學(xué)習(xí)源碼:https://github.com/zccodere/s... 第一章:應(yīng)用場(chǎng)景 1-1 多對(duì)多的應(yīng)用場(chǎng)景 案例分析:企業(yè)項(xiàng)目開(kāi)發(fā)過(guò)程中 一個(gè)項(xiàng)目可由多個(gè)員工參與開(kāi)發(fā) 一個(gè)員工可同時(shí)參與開(kāi)發(fā)多個(gè)項(xiàng)目 示意圖 showImg(https://segmentfau...
摘要:而用于主線程池的屬性都定義在中本篇只是簡(jiǎn)單介紹了一下引導(dǎo)類的配置屬性,下一篇我將詳細(xì)介紹服務(wù)端引導(dǎo)類的過(guò)程分析。 從Java1.4開(kāi)始, Java引入了non-blocking IO,簡(jiǎn)稱NIO。NIO與傳統(tǒng)socket最大的不同就是引入了Channel和多路復(fù)用selector的概念。傳統(tǒng)的socket是基于stream的,它是單向的,有InputStream表示read和Outpu...
閱讀 2333·2023-04-25 14:17
閱讀 1523·2021-11-23 10:02
閱讀 2175·2021-11-23 09:51
閱讀 884·2021-10-14 09:49
閱讀 3390·2021-10-11 10:57
閱讀 2928·2021-09-24 09:47
閱讀 3054·2021-08-24 10:00
閱讀 2305·2019-08-29 18:46