在linux 操做系統系統中幾乎全部IO操做都是以「文件」的形式管理的(一切皆文件),對「文件」的讀寫通常都要通過內核態和用戶態的切換,對於一次IO訪問(以read爲例),會經歷兩個階段:linux
階段一:調用操做系統的read方法,並開始阻塞等待,等待數據準備好(數據被拷貝到內核緩衝區);web
階段二:將數據從內核緩衝區拷貝到應用進程,應用進程進行處理。數據庫
Java 中的 BIO、NIO和 AIO 本質上是 Java 語言對操做系統層面的各類 IO 模型的封裝。windows
BIO是JDK1.4以前的傳統IO模型,屬於阻塞模式,服務器採用每一個鏈接由獨立線程維護的方式,即服務器收到客戶端的鏈接請求時,會啓動一個新的線程進行處理。在高併發場景下,機器資源很快會被耗盡,即便經過線程池來優化,也沒法改變阻塞IO的根本問題,即每一個線程在IO執行的上述兩個階段都被阻塞,系統的吞吐量也天然很難提高。服務器
NIO,能夠支持非阻塞IO。NIO採用了多路複用的IO模式,多路複用的模式在Linux底層能夠基於select、poll或epoll實現(本質都是一些系統函數),這種機制下能夠用單個線程監視多個fd(文件描述符),當其中一個fd讀寫就緒,會通知用戶線程進行IO操做。阻塞IO模式下阻塞的是每個用戶線程,與之不一樣的是,多路複用只須要阻塞一個用戶線程便可,這個用戶線程一般咱們叫它Selector,其實底層調用的是內核的select,只要任何一個IO操做就緒,就能夠喚醒select,而後交由用戶線程處理。用戶線程讀取數據這個過程仍然是阻塞的,多路複用技術只是在第一個階段能夠變爲非阻塞調用,但在第二個階段拷貝數據到用戶空間,其實仍是阻塞的,多路複用技術的最大特色是使用一個線程就能夠處理不少的socket鏈接,儘管性能上不必定提高,但併發能力和吞吐量卻大大加強了。併發
AIO,也被稱爲NIO2.0,在JDK1.7版本中發佈,提供了AIO的功能,支持文件的異步IO操做。從NIO中能夠看到,對於IO的兩個階段的阻塞,只是對於第一個階段有所改善,對於第二個階段在NIO裏面仍然是阻塞的。而理想的異步非阻塞IO要作的就是,將IO操做的兩個階段都所有交給內核系統完成,用戶線程只須要告訴內核,我要讀取一塊數據,請你幫我讀取,讀取完了放在我給你的地址裏面,而後告訴我一聲就能夠了。從操做系統層面來看,AIO算是真正的實現了異步非阻塞,操做系統層面的異步須要系統原生提供支持,目前windows基於IOCP(nput/Output Completion Port)技術實現,在Linux上,目前有不少開源的異步IO庫,例如libevent、libev、libuv,都不是基於操做系統的異步IO實現的,底層均是基於epoll實現的。異步
阻塞IO(blocking IO)模式下,用戶進程在發起系統調用直到數據到達且被拷貝到用戶空間的兩個階段都處於阻塞狀態。socket
非阻塞IO(noblocking IO)模式下,用戶進程持續非阻塞地詢問內核數據準備好了沒有,若是數據還沒準備好,用戶進程不會被block住,內核會當即返回error給用戶進程,直到數據準備就緒後,應用進程需同步等待數據從內核空間拷貝到用戶空間。async
IO多路複用(IO multiplexing)模式也稱做事件驅動IO。用戶進程會阻塞在select(或poll、epoll)系統調用上,內核會不斷的輪詢所負責的全部socket(或其餘文件描述符),當某個socket有數據到達了,就通知用戶進程,select調用就返回,這時候用戶進程再同步等待數據從內核空間拷貝到用戶空間。函數
IO多路複用和阻塞IO看起來貌似沒有什麼區別(兩個階段都是阻塞的),事實上還更差一些,由於這裏須要使用兩個系統調用(select和recvfrom),而阻塞IO只調用了一個系統調用(recvfrom)。可是,用select的優點在於它能夠同時處理多個鏈接。(因此,若是處理的鏈接數不是很高的話,使用select/poll/epoll的web server不必定比使用multi-threading + blocking IO的web server性能更好,select/epoll的優點並非對於單個鏈接能處理得更快,而是在於能處理更多的鏈接。)
信號驅動IO(signal-driven IO),使用信號機制,讓內核在描述符就緒時發送SIGIO信號通知用戶進程。整個過程是先經過sigaction系統調用安裝一個信號處理函數。該系統調用將當即返回,用戶進程繼續工做,也就是說它沒有被阻塞。當數據報準備好讀取時,內核就爲該進程產生一個SIGIO信號,咱們隨後能夠在信號處理函數中調用recvfrom讀取內核空間準備好的數據。特色:第一階段(等待數據報到達期間)進程不被阻塞。
異步IO(asynchronous IO)的工做機制是:告知內核開啓某個操做,並讓內核在整個操做完成後通知用戶進程,兩個階段都不會被阻塞。
與信號驅動IO模式的區別:信號驅動IO由內核通知咱們什麼時候啓動一個IO操做,異步IO由內核告訴咱們IO操做什麼時候完成。
前面說的5種IO模型的前四種,阻塞式I/O、非阻塞式I/O、I/O複用、信號驅動式I/O 在操做系統層面都是同步IO,它們都會阻塞在數據從內核空間複製到用戶空間的緩衝區;異步IO模型在兩個階段都不會阻塞調用進程,在操做系統層面實現真正的異步IO。
傳統的服務器,IO模型是採用爲每一個請求建立一個子線程來處理,這種模式在併發量小的狀況下能夠正常支撐業務,可是在高併發場景下,機器資源很快就會耗盡。現今常見的高吞吐高併發系統每每是基於事件驅動的IO多路複用模式設計,這種模式將全部的請求交給一個單獨的線程管理,此線程被稱之爲事件循環線程,當事件等待的系統資源就緒時會及時進行處理,而不是爲每一個鏈接生成一個OS線程。這種事件驅動的異步模型大幅度提高了服務器的吞吐能力,在相同配置的服務器能接受更多的併發請求。事件驅動模型的應用十分普遍,Redis就是一個典型的單線程基於事件驅動的內存數據庫,Node.js、Nginx、Netty等也都是基於這種方式來實現高吞吐性能。