本文由 GodPan 發表在 ScalaCool 團隊博客。java
Java IO對大多數Java程序員來講是熟悉又陌生,熟悉的是感受處處都有它的身影,小到簡單的讀取文件,大到各類服務器的應用,陌生的是Java IO背後究竟是一個怎樣的機制,今天就讓咱們去了解一下這位老朋友吧。本文不講解Java IO如何具體使用,有這方面需求的同窗能夠本身查下。程序員
要說IO,就不得不說IO模型,IO模型你們都有所瞭解,同步異步,阻塞非阻塞什麼的,總的來講IO模型可分爲如下五種:數據庫
那麼這幾種IO都有什麼區別呢?下面咱們一一來看,每種模型我都會舉一個適當的例子助於理解:編程
阻塞IO相信你們都最熟悉了,線程發起一個IO請求,直到有結果返回,不然則一直阻塞等待,好比咱們日常常見的阻塞數據庫操做,網絡IO等。服務器
小明阻塞IO吃飯:網絡
五年前一天週末,小明和朋友一塊兒去商場的外婆家吃飯,到店後發現排隊的人超多,因此他就領了一個號碼,而後他和朋友就坐在旁邊等候,一直等着服務員叫他們的號,也不能作其餘事,過了一個多小時終於輪到他們了,而後他們進店點菜,又得等待上菜,最後他們吃飯總共花了兩個小時;多線程
關鍵部分:異步
沒什麼說的,反正就是一直等,反應到程序中就是一直阻塞,而一個IO請求須要一個線程,可想而知當有大量的IO請求,線程的建立和銷燬,線程間的切換,線程所佔用的資源等等要耗費多少時間和資源,系統的性能會有多差。函數
非阻塞IO和阻塞IO的最大區別就在於線程發起一個IO請求,不會一直堵塞直到有數據,而是不斷的檢查是否已有數據,如有數據則讀取數據。工具
小明非阻塞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則不斷的觀察店裏的狀況,當有空座位出現的時候,他便會按照相關信息找到具體的顧客,將其帶回進行用餐,但他們進店點菜,還得等待上菜,最後他們吃飯總共花了兩個小時,可是他們再也不須要排隊等位,而是去作一些其餘的事。
關鍵部分:
多路複用IO能夠當作普通非阻塞IO的升級版,也是目前Java編程中用到比較多的IO模型,它的優點在於能夠處理大量的IO請求,用一個線程管理全部的IO請求,無需像阻塞IO和非阻塞IO同樣,每一個IO須要一個線程處理,提高了系統的吞吐量。
信號驅動IO相對於以上幾種模型最大的特色就是它支持內核信號通知,線程在發起一個IO請求後,會註冊一個信號函數,而後內核在確認數據可讀了,便會給相應的線程發送通知,讓其進行具體IO讀寫操做。
小明信號驅動IO吃飯:
又了一段時間,外婆家經過使用複用IO模式緩解了排隊擁擠的狀況,可是以爲還要請一我的專門維護隊列,感受不划算,那麼有沒有一種更好的方式呢?通過一天的苦思冥想,外婆家的經理又想出一個好辦法,讓每一個顧客在領完號後,關注一下外婆家的公衆號,而後顧客就能夠去作別的事了,定時或者當排隊信息發生改變時給顧客發送通知,告知他如今的排隊序號或者輪到他吃飯了,顧客能夠根據相應的信息作相應的行爲,好比快輪到了就開始往店裏走(實際程序中並不必定有這種狀態,這裏只是大概模擬),或者輪到本身了而後進店吃飯,他們仍然不用排隊等位,而是去作一些其餘的事。
關鍵部分:
就實際來講,信號驅動IO用的並很少,由於信號驅動IO底層是使用SIGIO信號,因此它主要使用在UDP協議上,由於UDP產生SIGIO信號的時候只有兩種可能:
但相對TCP來講,產生SIGIO信號的地方太多了,好比請求鏈接,確認,斷開,錯誤等等,因此咱們很難根據SIGIO信號判斷到底發生了什麼。
以上四種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 NIO,因此咱們須要瞭解操做系統是如何支持多路複用IO的,Linux中支持支持三種多路IO複用機制,分別是select、poll和epoll,原本這裏我想本身寫的,但查閱了相應的一些資料後,發現本身的水平仍是不夠,這裏我不許備班門弄斧了,由於我找到了不少寫的比較好的文章,這裏就給你們列一下,僅供參考:
這篇文章主要講了最基礎的IO模型,不過我認爲最基礎的每每是最重要的,只有理解了基礎的原理,才能對基於它們實現的類庫或者工具備更加深入的認識,下一篇文章將會主要講一下基於多路複用IO的Java NIO,敬請期待。