Java IO初探

Java IO對大多數Java程序員來說是熟悉又陌生,熟悉的是感覺到處都有它的身影,小到簡單的讀取文件,大到各種服務器的應用,陌生的是Java IO背后到底是一個怎樣的機制,今天就讓我們去了解一下這位老朋友吧。本文不講解Java IO如何具體使用,有這方面需求的同學可以自己查下。

IO模型

要說IO,就不得不說IO模型,IO模型大家都有所了解,同步異步,阻塞非阻塞什么的,總的來說IO模型可分為以下五種:

  • 阻塞IO
  • 非阻塞IO
  • 多路復用IO
  • 信號驅動IO
  • 異步IO

那么這幾種IO都有什么區別呢?下面我們一一來看,每種模型我都會舉一個適當的例子助于理解:

1.阻塞IO

阻塞IO相信大家都最熟悉了,線程發起一個IO請求,直到有結果返回,否則則一直阻塞等待,比如我們平常常見的阻塞數據庫操作,網絡IO等。

小明阻塞IO吃飯:

五年前一天周末,小明和朋友一起去商場的外婆家吃飯,到店后發現排隊的人超多,所以他就領了一個號碼,然后他和朋友就坐在旁邊等候,一直等著服務員叫他們的號,也不能做其他事,過了一個多小時終于輪到他們了,然后他們進店點菜,又得等待上菜,最后他們吃飯總共花了兩個小時;

關鍵部分:

  • 等待座位吃飯:一直阻塞,直到有座位
  • 等待上菜:一直阻塞,直到有菜(假設菜上齊了再吃)

沒什么說的,反正就是一直等,反應到程序中就是一直阻塞,而一個IO請求需要一個線程,可想而知當有大量的IO請求,線程的創建和銷毀,線程間的切換,線程所占用的資源等等要耗費多少時間和資源,系統的性能會有多差。

2.非阻塞IO

非阻塞IO和阻塞IO的最大區別就在于線程發起一個IO請求,不會一直堵塞直到有數據,而是不斷的檢查是否已有數據,若有數據則讀取數據。

小明非阻塞IO吃飯:

有了第一次的教訓,小明學乖了,他在拿到后不再傻傻的等著,而是去外婆家旁邊逛了逛,每過3分鐘他就會回來,然后跑到前臺去詢問服務員輪到他了嗎?不幸的是,排隊的人超多,直到過了半個多小時后才輪到他進店吃飯,期間他大概問了十幾次,他們進店點菜,又得等待上菜,最后他們吃飯總共花了兩個小時,基本也沒做啥其他事;

關鍵部分:

  • 領號后詢問是否輪到他:非阻塞,非詢問期間可以做點別的事,但也不做了啥大事
  • 等待上菜:一直阻塞,直到有菜(假設菜上齊了再吃)

總的來說非阻塞IO的非阻塞主要體現在不需要一直等待到有數據,當然讀數據那部分操作還是阻塞的,另外這種非阻塞模式需要用戶線程自己不斷詢問檢查,其實效率也不是太高,實際編程中運用的也不多。

3.多路復用IO

既然上面我們說到非阻塞IO的缺點,那么有沒有什么方式改進呢?答案是當然有,那就是多路復用IO,我理解的它的特點就是復用,首先它也是一種非阻塞IO的模型,只不過上面說到輪詢的方式用了不同的方式處理了,當一個線程發起IO請求,系統會將它注冊到一個單獨管理IO請求的一個線程,之后該IO的相關操作的通知狀態都有這個管理IO請求的線程處理,Java 1.4發布的NIO就是這種模式,我們可以大致來看一下它的流程:

// 打開服務器套接字通道
ServerSocketChannel ssc = ServerSocketChannel.open();
// 服務器配置為非阻塞
ssc.configureBlocking(false);
// 進行服務的綁定
ssc.bind(new InetSocketAddress("localhost", 8008));
// 這里的selector就相當于單獨管理IO請求的線程
Selector selector = Selector.open();
// 注冊到selector,等待連接
ssc.register(selector, SelectionKey.OP_ACCEPT);

while (true) {
    selector.select();  //為IO請求去輪詢狀態
    Set<SelectionKey> keys = selector.selectedKeys(); //多個IO請求的狀態
    Iterator<SelectionKey> keyIterator = keys.iterator();
    while (keyIterator.hasNext()) { //依次處理IO請求
        SelectionKey key = keyIterator.next();
        doThing(key)
        ...
    }
}

可以看出Java NIO的模式就是多路復用IO模型的應用。

小明多路復用IO吃飯:

隨著生意越來越好,外婆家發現好多顧客都堵在門口等待吃飯,等待區都站不下來人了,,思來想去,外婆家準備請一個人專門來維護顧客的排隊請求,這樣顧客取號后,就不用堵在門口了,我們叫他小A,小明這次取號后,將自己的相關信息告訴小A,并從小A那里獲得了一個GPS(用于小A能快速找到小明,假設有了GPS后,小A能秒速找到小明),然后小明就跟朋友們開心的去逛商場,看看MM,買買衣服,而小A則不斷的觀察店里的情況,當有空座位出現的時候,他便會按照相關信息找到具體的顧客,將其帶回進行用餐,但他們進店點菜,還得等待上菜,最后他們吃飯總共花了兩個小時,但是他們不再需要排隊等位,而是去做一些其他的事。

關鍵部分:

  • 領號后委托給小A,小A觀察到有空位后帶回小明:非阻塞,領號后可以安心去做自己的事,不用擔心錯過
  • 等待上菜:一直阻塞,直到有菜(假設菜上齊了再吃)

多路復用IO可以看成普通非阻塞IO的升級版,也是目前Java編程中用到比較多的IO模型,它的優勢在于可以處理大量的IO請求,用一個線程管理所有的IO請求,無需像阻塞IO和非阻塞IO一樣,每個IO需要一個線程處理,提升了系統的吞吐量。

4.信號驅動IO

信號驅動IO相對于以上幾種模型最大的特點就是它支持內核信號通知,線程在發起一個IO請求后,會注冊一個信號函數,然后內核在確認數據可讀了,便會給相應的線程發送通知,讓其進行具體IO讀寫操作。

小明信號驅動IO吃飯:

又了一段時間,外婆家通過使用復用IO模式緩解了排隊擁擠的情況,但是覺得還要請一個人專門維護隊列,感覺不劃算,那么有沒有一種更好的方式呢?經過一天的苦思冥想,外婆家的經理又想出一個好辦法,讓每個顧客在領完號后,關注一下外婆家的公眾號,然后顧客就可以去做別的事了,定時或者當排隊信息發生改變時給顧客發送通知,告知他現在的排隊序號或者輪到他吃飯了,顧客可以根據相應的信息做相應的行為,比如快輪到了就開始往店里走(實際程序中并不一定有這種狀態,這里只是大概模擬),或者輪到自己了然后進店吃飯,他們仍然不用排隊等位,而是去做一些其他的事。

關鍵部分:

  • 領號后關注公眾號,注冊關系:非阻塞,領號后可以安心去做自己的事,不用擔心錯過
  • 等待上菜:一直阻塞,直到有菜(假設菜上齊了再吃)

就實際來說,信號驅動IO用的并不多,因為信號驅動IO底層是使用SIGIO信號,所以它主要使用在UDP協議上,因為UDP產生SIGIO信號的時候只有兩種可能:

  • 1.要么數據到達
  • 2.發生錯誤

但相對TCP來說,產生SIGIO信號的地方太多了,比如請求連接,確認,斷開,錯誤等等,所以我們很難根據SIGIO信號判斷到底發生了什么。

5.異步IO

以上四種IO其實都還是同步IO,因為它們在讀寫數據時都是阻塞的,異步IO相較于它們最大的特點是它讀寫數據的時候也是非阻塞的,用戶線程在發起一個IO請求的時候,除了給內核線程傳遞具體的IO請求外,還會給其傳遞數據緩沖區,回調函數通知等內容,然后用戶線程就繼續執行,等到內核線程發起相應通知的時候,說明數據已經準備就緒,用戶線程直接使用即可,無需再阻塞從內核拷貝數據到用戶線程。

小明異步IO吃飯:

有過了一段時間,小明又想吃外婆家了,但是這個周末他并不想出門,他突然在網上看到新聞說外婆家竟然可以叫外賣,小明高興壞了,他馬上打電話給外婆家,告訴它自己想要吃哪些菜(相當于IO請求所需要的數據),然后將自己的聯系號碼(相當于回調通知)和住址(相當于數據緩沖區)也告訴它,然后就掛掉電話,開心的做去打游戲了,過了半個小時后,手機響起,告知外賣已經到了,小明開門取外賣就可以直接開吃了。整個過程小明直到吃飯都沒有等待阻塞。

關鍵部分:

  • 叫外賣并提供相應的信息:非阻塞,打完電話后做自己的事
  • 通知外賣到了:直接開門取外賣直接開吃,非阻塞

我們可以看出,異步IO才是真正的異步,因為它連數據拷貝這個過程都是非阻塞的,用戶線程根本不用關心數據的讀寫等操作,只需等待內核線程通知后,直接處理數據即可,當然異步IO需要系統內核支持,比如Linux中的AIO和Windows中的IOCP,但是也可以通過多線程跟阻塞I/O模擬異步IO,比如可以在多路復用IO模型上進行相應的改變,另外也有現有的實現,比如異步I/O的庫:libeio

最后用一張圖總體概括一下Java IO(圖片來自美團技術博客):

Java IO概圖:

java-io

多路復用IO在Linux中的實現

因為后續會講到Java NIO,所以我們需要了解操作系統是如何支持多路復用IO的,Linux中支持支持三種多路IO復用機制,分別是select、poll和epoll,本來這里我想自己寫的,但查閱了相應的一些資料后,發現自己的水平還是不夠,這里我不準備班門弄斧了,因為我找到了很多寫的比較好的文章,這里就給大家列一下,僅供參考:

總結

這篇文章主要講了最基礎的IO模型,不過我認為最基礎的往往是最重要的,只有理解了基礎的原理,才能對基于它們實現的類庫或者工具有更加深刻的認識,下一篇文章將會主要講一下基于多路復用IO的Java NIO,敬請期待。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容