傳統同步阻塞I/O模型:html
{ ExecutorService executor = Excutors.newFixedThreadPollExecutor(100);//線程池 ServerSocket serverSocket = new ServerSocket(); serverSocket.bind(8088); while(!Thread.currentThread.isInturrupted()){//主線程死循環等待新鏈接到來 Socket socket = serverSocket.accept(); executor.submit(new ConnectIOnHandler(socket));//爲新的鏈接建立新的線程 } class ConnectIOnHandler extends Thread{ private Socket socket; public ConnectIOnHandler(Socket socket){ this.socket = socket; } public void run(){ while(!Thread.currentThread.isInturrupted()&&!socket.isClosed()){//死循環處理讀寫事件 String someThing = socket.read()....//讀取數據 if(someThing!=null){ ......//處理數據 socket.write()....//寫數據 } } } }
之因此使用多線程,主要緣由在於socket.accept()、socket.read()、socket.write()三個主要函數都是同步阻塞的,當一個鏈接在處理I/O的時候,系統是阻塞的。java
若是是單線程的話必然就掛死在那裏,但CPU是被釋放出來的,開啓多線程,就可讓CPU去處理更多的事情。數組
這個模型最本質的問題在於,嚴重依賴於線程。但線程是很"貴"的資源,主要表如今:緩存
線程的建立和銷燬成本很高,在Linux這樣的操做系統中,線程本質上就是一個進程。建立和銷燬都是重量級的系統函數。服務器
線程自己佔用較大內存,像Java的線程棧,通常至少分配512K~1M的空間,若是系統中的線程數過千,恐怕整個JVM的內存都會被吃掉一半。網絡
線程的切換成本是很高的。操做系統發生線程切換的時候,須要保留線程的上下文,而後執行系統調用。若是線程數太高,可能執行線程切換的時間甚至會大於線程執行的時間,這時候帶來的表現每每是系統load偏高、CPU sy使用率特別高(超過20%以上),致使系統幾乎陷入不可用的狀態。多線程
容易形成鋸齒狀的系統負載。由於系統負載是用活動線程數或CPU核心數,一旦線程數量高但外部網絡環境不是很穩定,就很容易形成大量請求的結果同時返回,激活大量阻塞線程從而使系統負載壓力過大。dom
因此,當面對十萬甚至百萬級鏈接的時候,傳統的BIO模型是無能爲力的。異步
NIO簡介: socket
Java NIO 是 java 1.4, 以後新出的一套IO接口NIO中的N能夠理解爲Non-blocking,不單純是New。
NIO的特性/NIO與IO區別:
IO是面向流的,NIO是面向緩衝區的;
IO流是阻塞的,NIO流是不阻塞的;
NIO有選擇器,而IO沒有。
讀數據和寫數據方式:
從通道進行數據讀取 :建立一個緩衝區,而後請求通道讀取數據。
從通道進行數據寫入 :建立一個緩衝區,填充數據,並要求通道寫入數據。
其中Java最先提供的blocking I/O便是阻塞I/O,而NIO便是非阻塞I/O,同時經過NIO實現的Reactor模式便是I/O複用模型的實現,經過AIO實現的Proactor模式便是異步I/O模型的實現。
Java IO是面向流的,每次從流(InputStream/OutputStream)中讀一個或多個字節,直到讀取完全部字節,它們沒有被緩存在任何地方。另外,它不能先後移動流中的數據,如需先後移動處理,須要先將其緩存至一個緩衝區。
Java NIO面向緩衝,數據會被讀取到一個緩衝區,須要時能夠在緩衝區中先後移動處理,這增長了處理過程的靈活性。但與此同時在處理緩衝區前須要檢查該緩衝區中是否包含有所須要處理的數據,並須要確保更多數據讀入緩衝區時,不會覆蓋緩衝區內還沒有處理的數據。
參考:http://www.javashuo.com/article/p-zknvkujy-n.html
NIO(Non-blocking I/O):
是一種同步非阻塞的I/O模型。
NIO主要有三大核心部分:Channel(通道),Buffer(緩衝區), Selector。
傳統IO基於字節流和字符流進行操做,而NIO基於Channel和Buffer(緩衝區)進行操做,數據老是從通道讀取到緩衝區中,或者從緩衝區寫入到通道中。
Selector(選擇區)用於監聽多個通道的事件(好比:鏈接打開,數據到達)。所以,單個線程能夠監聽多個數據通道。
NIO和傳統BIO之間第一個最大的區別是,BIO是面向流的,NIO是面向緩衝區的。
BIO面向流意味着每次從流中讀一個或多個字節,直至讀取全部字節,它們沒有被緩存在任何地方。
BIO的各類流是阻塞的。這意味着,當一個線程調用read() 或 write()時,該線程被阻塞,直到有一些數據被讀取,或數據徹底寫入。該線程在此期間不能再幹任何事情了。
NIO的非阻塞,使一個線程從某通道發送請求讀取數據,可是它僅能獲得目前可用的數據,若是目前沒有數據可用時,就什麼都不會獲取。而不是保持線程阻塞,因此直至數據變的能夠讀取以前,該線程能夠繼續作其餘的事情。
線程一般將非阻塞IO的空閒時間用於在其它通道上執行IO操做,因此一個單獨的線程如今能夠管理多個輸入和輸出通道(channel)。
Channel:
Channel和BIO中的Stream(流)是差很少一個等級的。只不過Stream是單向的,好比:InputStream, OutputStream.而Channel是雙向的。
NIO中的Channel的主要實現有:FileChannel,DatagramChannel,SocketChannel,ServerSocketChannel。
詳解:https://zhuanlan.zhihu.com/p/27365009
Buffer:
NIO中的關鍵Buffer實現有:ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer,分別對應基本數據類型: byte, char, double, float, int, long, short。
緩衝區實質上是一個數組。一般它是一個字節數組,可是也可使用其餘種類的數組。
詳解:https://zhuanlan.zhihu.com/p/27296046
Selector:
Selector運行單線程處理多個Channel,若是你的應用打開了多個通道,但每一個鏈接的流量都很低,使用Selector就會很方便。
例如在一個聊天服務器中。要使用Selector, 得向Selector註冊Channel,而後調用它的select()方法。這個方法會一直阻塞到某個註冊的通道有事件就緒。
一旦這個方法返回,線程就能夠處理這些事件,事件的例子有如新的鏈接進來、數據接收等。
詳解:https://zhuanlan.zhihu.com/p/27434028
讀取文件涉及三個步驟:
(1) 從 FileInputStream 獲取 Channel(也能夠用RandomAccessFile),(2) 建立 Buffer,(3) 將數據從 Channel 讀到 Buffer中。
1 public static void method1(){ 2 RandomAccessFile aFile = null; 3 try{ 4 aFile = new RandomAccessFile("src/nio.txt","rw"); 5 FileChannel fileChannel = aFile.getChannel(); 6 ByteBuffer buf = ByteBuffer.allocate(1024); 7 8 int bytesRead = fileChannel.read(buf); 9 System.out.println(bytesRead); 10 11 while(bytesRead != -1) 12 { 13 buf.flip(); 14 while(buf.hasRemaining()) 15 { 16 System.out.print((char)buf.get()); 17 } 18 19 buf.compact(); 20 bytesRead = fileChannel.read(buf); 21 } 22 }catch (IOException e){ 23 e.printStackTrace(); 24 }finally{ 25 try{ 26 if(aFile != null){ 27 aFile.close(); 28 } 29 }catch (IOException e){ 30 e.printStackTrace(); 31 } 32 } 33 }
NIO事件模型:
NIO的主要事件有幾個:讀就緒、寫就緒、有新鏈接到來。
咱們首先須要註冊當這幾個事件到來的時候所對應的處理器。而後在合適的時機告訴事件選擇器:我對這個事件感興趣。
新事件到來的時候,會在selector上註冊標記位,標示可讀、可寫或者有鏈接到來。
select是阻塞的,不管是經過操做系統的通知(epoll)仍是不停的輪詢(select,poll),這個函數是阻塞的。
1 interface ChannelHandler{ 2 void channelReadable(Channel channel); 3 void channelWritable(Channel channel); 4 } 5 class Channel{ 6 Socket socket; 7 Event event;//讀,寫或者鏈接 8 } 9 10 //IO線程主循環: 11 class IoThread extends Thread{ 12 public void run(){ 13 Channel channel; 14 while(channel=Selector.select()){//選擇就緒的事件和對應的鏈接 15 if(channel.event==accept){ 16 registerNewChannelHandler(channel);//若是是新鏈接,則註冊一個新的讀寫處理器 17 } 18 if(channel.event==write){ 19 getChannelHandler(channel).channelWritable(channel);//若是能夠寫,則執行寫事件 20 } 21 if(channel.event==read){ 22 getChannelHandler(channel).channelReadable(channel);//若是能夠讀,則執行讀事件 23 } 24 } 25 } 26 Map<Channel,ChannelHandler> handlerMap;//全部channel的對應事件處理器 27 }