主要分爲3種:BIO(同步阻塞)、NIO(同步非阻塞)和AIO(異步非阻塞)。linux
同步阻塞模式。在JDK1.4之前,使用Java創建網絡鏈接時,只能採用BIO方式,在服務器端啓動一個ServerSocket,而後使用accept等待客戶端請求,對於每個請求,使用一個線程來進行處理用戶請求。線程的大部分時間都在等待請求的到來和IO操做,利用率很低。並且線程的開銷比較大,數量有限,所以服務器同時能處理的鏈接數也很低。緩存
BIO模式中,是「一個Socket一個線程」;而在NIO中則是使用單個或少許的線程來輪詢Socket,當發現Socket上有請求時,才爲請求分配線程。所以是「一個請求一個線程」。服務器
具體實現就是把Socket經過Channel註冊到Selector,使用一個線程在Selector中輪詢,發現Channel有讀寫的事件,就能夠分配給其餘線程來處理(一般使用線程池)。網絡
從JDK7開始支持AIO模式。經過AsynchronousServerSocketChannel中註冊事件回調函數來處理業務邏輯。當IO操做完成之後,回調函數會被調用。若是傳入AsynchronousChannelGroup,能夠綁定線程池來處理事件。多線程
關於JDK的實現,Windows平臺基於IOCP實現AIO,Linux只有eppoll模擬實現了AIO。異步
用一句話來總結這三種IO的區別:socket
以銀行取款爲例: 函數
系統I/O 可分爲阻塞型, 非阻塞同步型以及非阻塞異步型。性能
阻塞型I/O意味着控制權只到調用操做結束了纔會回到調用者手裏。 結果調用者被阻塞了, 這段時間了作不了任何其它事情。 更鬱悶的是,在等待IO結果的時間裏,調用者所在線程此時沒法騰出手來去響應其它的請求,這真是太浪費資源了。拿read()操做來講吧, 調用此函數的代碼會一直僵在此處直至它所讀的socket緩存中有數據到來。操作系統
相比之下,非阻塞同步是會當即返回控制權給調用者的。調用者不須要等等,它從調用的函數獲取兩種結果:要麼這次調用成功進行了;要麼系統返回錯誤標識告訴調用者當前資源不可用,你再等等或者再試度看吧。好比read()操做, 若是當前socket無數據可讀,則當即返回EWOULBLOCK/EAGAIN,告訴調用read()者」數據還沒準備好,你稍後再試」。
在非阻塞異步調用中,稍有不一樣。調用函數在當即返回時,還告訴調用者,此次請求已經開始了。系統會使用另外的資源或者線程來完成此次調用操做,並在完成的時候知會調用者(好比經過回調函數)。拿Windows的ReadFile()或者POSIX的aio_read()來講,調用它以後,函數當即返回,操做系統在後臺同時開始讀操做。
在以上三種IO形式中,非阻塞異步是性能最高、伸縮性最好的。
通常狀況下,I/O 複用機制須要事件分享器(event demultiplexor )。 事件分享器的做用,即將那些讀寫事件源分發給各讀寫事件的處理者,就像送快遞的在樓下喊: 誰的什麼東西送了, 快來拿吧。開發人員在開始的時候須要在分享器那裏註冊感興趣的事件,並提供相應的處理者(event handlers),或者是回調函數; 事件分享器在適當的時候會將請求的事件分發給這些handler或者回調函數。
涉及到事件分享器的兩種模式稱爲:Reactor and Proactor 。 Reactor模式是基於同步I/O的,而Proactor模式是和異步I/O相關的。 在Reactor模式中,事件分離者等待某個事件或者可應用或個操做的狀態發生(好比文件描述符可讀寫,或者是socket可讀寫),事件分離者就把這個事件傳給事先註冊的事件處理函數或者回調函數,由後者來作實際的讀寫操做。
Reator類圖如上所示,Reactor模式又叫反應器或反應堆,即實現註冊描述符(Handle)及事件的處理器(EventHandler),當有事件發生的時候,事件多路分發器(Event Demultiplexer)作出反應,調用事件具體的處理函數(ConcreteEventHandler::handle_event())。
Reator模式的典型啓動過程以下:
Reactor模式已經被普遍使用,著名的開源事件庫libevent、libev、libuv都是使用Reactor模式。
Proactor模式的類圖如上圖所示,Proactor模式又叫前攝器或主動器模式。它用於實現異步I/O模型,運行流程以下:
Boost庫中的asio就使用了Proactor模式,其底層的異步I/O由操做系統提供,而異步事件的分發仍是由epoll/kequeue/select等實現。
在Proactor模式中,事件處理者(或者代由事件分離者發起)直接發起一個異步讀寫操做(至關於請求),而實際的工做是由操做系統來完成的。發起時,須要提供的參數包括用於存放讀到數據的緩存區,讀的數據大小,或者用於存放外發數據的緩存區,以及這個請求完後的回調函數等信息。事件分離者得知了這個請求,它默默等待這個請求的完成,而後轉發完成事件給相應的事件處理者或者回調。
主要區別:
若是隻是處理網絡I/O單線程的Reactor尚可處理,但若是涉及到文件I/O,單線程的Reactor可能被文件I/O阻塞而致使其餘事件沒法被分發。因此涉及到文件I/O最好仍是使用Proactor模式,或者用多線程模擬實現異步I/O的方式。
即Reactor模式有事件發生的時候要判斷是讀事件仍是寫事件,而後用再調用系統調用(read/write等)將數據從內核中拷貝到用戶數據區繼續其餘業務處理。
而Proactor模式通常使用的是操做系統的異步I/O接口,發起異步調用(用戶提供數據緩衝區)以後操做系統將在內核態完成I/O並拷貝數據到用戶提供的緩衝區中,完成事件到達以後,用戶只須要實現本身後續的業務處理便可。
Reactor模式是一種被動的處理,即有事件發生時被動處理。而Proator模式則是主動發起異步調用,而後循環檢測完成事件。
咱們知道linux系統提供的異步I/O,只支持O_DIRECT,不能帶緩存。所以出現了開源庫libeio,它和Linux的異步I/O同樣也是用多線程模擬,可是更高效。下圖是libeio的異步I/O實現,是否是很像Proactor模式啊。