BIO:java
BIO是阻塞IO,體如今一個線程調用IO的時候,會掛起等待,而後Thread會進入blocked狀態;這樣線程資源就會被閒置,形成資源浪費,一般一個系統線程數是有限的,並且,Thread進入內核態也是很大的性能開銷。而阻塞方式,意味着BIO必然是一個同步IO。express
BIO還有一個顯著的特色是面向流式Stream編程,特色是實現簡單,但也意味着拓展性差。編程
NIO:網絡
NIO,一般實現爲同步非阻塞IO,同步意味着不會產生會調,須要線程自身去同步IO是否完成,而非阻塞就是線程會馬上返回。多線程
相對於BIO面向流式抽象思想編程,NIO是面向管道編程的,例如在Java中必談的三個封裝類Buffer、Channel、Sellector,就是管道編程的體現,Java1.4後提供的非阻塞 IO 的核心在於使用一個 Selector 來管理多個通道,能夠是 SocketChannel,也能夠是 ServerSocketChannel,將各個通道註冊到 Selector 上,指定監聽的事件。以後能夠只用一個線程來輪詢這個 Selector,看看上面是否有通道是準備好的,當通道準備好可讀或可寫,而後纔去開始真正的讀寫,這樣速度就很快了。咱們就徹底沒有必要給每一個通道都起一個線程。以下面代碼所示:併發
1 package com.mobisummer.spider.slave.task.aliexpress.region; 2 3 import java.io.IOException; 4 import java.net.InetSocketAddress; 5 import java.net.ServerSocket; 6 import java.nio.ByteBuffer; 7 import java.nio.channels.SelectionKey; 8 import java.nio.channels.Selector; 9 import java.nio.channels.ServerSocketChannel; 10 import java.nio.channels.SocketChannel; 11 import java.util.Iterator; 12 import java.util.Set; 13 14 public class PlainNioServer { 15 16 public void serve(int port) throws IOException { 17 18 ServerSocketChannel serverChannel = ServerSocketChannel.open(); 19 serverChannel.configureBlocking(false); 20 ServerSocket ssocket = serverChannel.socket(); 21 InetSocketAddress address = new InetSocketAddress(port); 22 ssocket.bind(address); 23 Selector selector = Selector.open(); 24 serverChannel.register(selector, SelectionKey.OP_ACCEPT); 25 final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes()); 26 27 for (; ; ) { 28 try { 29 selector.select(); 30 } catch (IOException ex) { 31 ex.printStackTrace(); 32 // handle exception 33 break; 34 } 35 Set<SelectionKey> readyKeys = selector.selectedKeys(); 36 Iterator<SelectionKey> iterator = readyKeys.iterator(); 37 while (iterator.hasNext()) { 38 SelectionKey key = iterator.next(); 39 iterator.remove(); 40 try { 41 if (key.isAcceptable()) { 42 ServerSocketChannel server = (ServerSocketChannel) key.channel(); 43 SocketChannel client = server.accept(); 44 client.configureBlocking(false); 45 client.register(selector, 46 SelectionKey.OP_WRITE | SelectionKey.OP_READ, 47 msg.duplicate()); 48 System.out.println("Accepted connection from " + client); 49 } 50 if (key.isWritable()) { 51 SocketChannel client = (SocketChannel) key.channel(); 52 ByteBuffer buffer = (ByteBuffer) key.attachment(); 53 while (buffer.hasRemaining()) { 54 if (client.write(buffer) == 0) { 55 break; 56 } 57 } 58 client.close(); 59 } 60 } catch (IOException ex) { 61 key.cancel(); 62 try { 63 key.channel().close(); 64 } catch (IOException cex) { 65 // ignore on close 66 } 67 } 68 } 69 } 70 } 71 }
對於併發數量大但處理的任務又十分快速的時候用處十分顯著,代替了以前的利用多線程解決業務問題的方案,就是利用單線程以及底層epoll或者poll原理完成了單線程處理多任務的方案,理論上至少咱們想到了減小線程切換的開支,而由內核去改變IO狀態。框架
【說說實現】異步
NIO 中 Selector 是對底層操做系統實現的一個抽象,管理通道狀態其實都是底層系統實現的,在不一樣系統下的實現會不一樣,是自動選擇的,可能的實現方式以下:socket
select:上世紀 80 年代的事情了,它支持註冊 FD_SETSIZE(1024) 個 socket,在那個年代確定是夠用的。ide
poll:1997 年,出現了 poll 做爲 select 的替代者,最大的區別就是,poll 再也不限制 socket 數量。
select 和 poll 都有一個共同的問題,那就是它們都只會告訴你有幾個通道準備好了,可是不會告訴你具體是哪幾個通道。因此,一旦知道有通道準備好之後,本身仍是須要進行一次掃描,顯然這個不太好,通道少的時候還行,一旦通道的數量是幾十萬個以上的時候,掃描一次的時間都很可觀了,時間複雜度 O(n)。因此,後來才催生了如下實現。
epoll:2002 年隨 Linux 內核 2.5.44 發佈,epoll 能直接返回具體的準備好的通道,時間複雜度 O(1)。那麼這個epoll是怎麼的原理呢?這就涉及操做系統的中斷了,在內核的最底層是中斷,相似系統回調的機制。網卡設備對應一箇中斷號, 當網卡收到網絡端的消息的時候會向CPU發起中斷請求, 而後CPU處理該請求. 經過驅動程序 進而操做系統獲得通知, 系統而後通知epoll, epoll改變阻塞狀態。
除了 Linux 中的 epoll,2000 年 FreeBSD 出現了 Kqueue,還有就是,Solaris 中有 /dev/poll。
前面說了那麼多實現,可是沒有出現 Windows,Windows 平臺的非阻塞 IO 使用 select,咱們也沒必要以爲 Windows 很落後,在 Windows 中 IOCP 提供的異步 IO 是比較強大的。
AIO:
異步這個詞,我想對於絕大多數開發者來講都很熟悉,不少場景下咱們都會使用異步。對於我而言比較有意義的事情就是發現我所在公司本身作的底層框架Lwmf,本身作了一個聲稱爲AIO的實現,只不過是封裝了一層罷。
一般,咱們會有一個線程池用於執行異步任務,提交任務的線程將任務提交到線程池就能夠立馬返回,沒必要等到任務真正完成。若是想要知道任務的執行結果,一般是經過傳遞一個回調函數的方式,任務結束後去調用這個函數。
一樣的原理,Java 中的異步 IO 也是同樣的,都是由一個線程池來負責執行任務,而後使用回調或本身去查詢結果,因此這裏涉及了兩個實現方式,在Java中就是註冊回調函數和使用異步任務返回的Feature實例。
乾貨在這裏:對象是過程的抽象,而線程是調度的抽象;因此,設計異步IO的時候,須要把線程控制的緊緊的,才能更穩健的設計哦。
最後,不得不提一下的就是Reactor模型和Netty框架了!但不是本文重點,但這確實是java中優秀的NIO實現