【谈谈IO】BIO、NIO和AIO
BIO:
BIO是阻塞IO,體現在一個線程調用IO的時候,會掛起等待,然后Thread會進入blocked狀態;這樣線程資源就會被閑置,造成資源浪費,通常一個系統線程數是有限的,而且,Thread進入內核態也是很大的性能開銷。而阻塞方式,意味著BIO必然是一個同步IO。
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 是對底層操作系統實現的一個抽象,管理通道狀態其實都是底層系統實現的,在不同系統下的實現會不同,是自動選擇的,可能的實現方式如下:
select:上世紀 80 年代的事情了,它支持注冊 FD_SETSIZE(1024) 個 socket,在那個年代肯定是夠用的。
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實現
?
轉載于:https://www.cnblogs.com/iCanhua/p/8547133.html
總結
以上是生活随笔為你收集整理的【谈谈IO】BIO、NIO和AIO的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 爬虫(十二):scrapy中spider
- 下一篇: HDOJ 1012-1020