寫在前面,這裏所說的IO主要是強調的網絡IOjava
1.BIO(同步並阻塞)數據庫
客戶端一個請求對應一個線程。客戶端上來一個請求(最開始的鏈接以及後續的IO請求),服務端新建一個線程去處理這個請求,因爲線程總數是有限的(操做系統對線程總數的限制或者線程池的大小),因此,當達到最大值時給客戶端的反饋就是沒法響應,阻塞體如今服務端接收客戶端鏈接請求被阻塞了,還有一種阻塞是在單線程處理某一個鏈接時,須要一直等待IO操做完成。同步體如今單個線程處理請求時調用read(write)方法需等待讀取(寫)操做完成才能返回。編程
2.NIO(同步非阻塞)後端
客戶端一個IO請求對應一個線程。過程是這樣的,每一個客戶端一開始上來的鏈接會註冊到selector中,selector會輪詢註冊上來的鏈接是否有IO請求,若是有IO請求,就建立一個線程處理該鏈接上的該次請求。非阻塞體如今服務端可以無限量(相對於BIO)的接收客戶端的鏈接請求。同步體如今單個線程處理請求時調用read(write)方法需等待讀取(寫)操做完成才能返回。這種模式下,若是後端應用處理遇到資源爭奪(數據庫操做)而阻塞等,爲提升請求的處理速度,能夠在後端設立資源池或隊列等,把對應的請求數據以及現場(哪一個鏈接的哪一個請求等)放入隊列,前臺線程當即返回處理別的IO請求。緩存
3.AIO(NIO2)(異步阻塞)服務器
客戶端一個IO請求對應一個線程。過程同NIO,只是在讀寫IO時略有差別,對於read,方法調用後會當即返回,返回對應中有個回調方法,調用時java會告知操做系統緩衝區大小以及地址,操做系統把流裏面的內容讀入緩衝區後回調剛剛read返回的回調方法。對應write,方法調用後會當即返回,返回對應中有個回調方法,調用時將數據放入緩存區,操做系統往流裏面寫完數據後一樣會回調剛剛wirte返回的回調方法。阻塞是由於此時是經過select系統調用來完成的,而select函數自己的實現方式是阻塞的,而採用select函數有個好處就是它能夠同時監聽多個文件句柄,從而提升系統的併發性請求。異步步體如今單個線程處理請求時調用read(write)方法會當即返回,返回對象有回調方法,底層OS完成IO操做後會回調該方法。網絡
適用場景:架構
BIO方式適用於鏈接數目比較小且固定的架構,這種方式對服務器資源要求比較高,併發侷限於應用中,JDK1.4之前的惟一選擇,但程序直觀簡單易理解。併發
NIO方式適用於鏈接數目多且鏈接比較短(輕操做)的架構,好比聊天服務器,併發侷限於應用中,編程比較複雜,JDK1.4開始支持。異步
AIO方式使用於鏈接數目多且鏈接比較長(重操做)的架構,好比相冊服務器,充分調用OS參與併發操做,編程比較複雜,JDK7開始支持。
在JDK1.7中,這部份內容被稱做NIO.2,主要在java.nio.channels包下增長了下面四個異步通道:
注:I/O屬於底層操做,須要操做系統支持,併發也須要操做系統的支持,因此性能方面不一樣操做系統差別會比較明顯。
在高性能的I/O設計中,有兩個比較著名的模式Reactor和Proactor模式,其中Reactor模式用於同步I/O,而Proactor運用於異步I/O操做。
附錄:java NIO示例服務端: package cn.nio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; /** * NIO服務端 * */ public class NIOServer { //通道管理器 private Selector selector; /** * 得到一個ServerSocket通道,並對該通道作一些初始化的工做 * @param port 綁定的端口號 * @throws IOException */ public void initServer(int port) throws IOException { // 得到一個ServerSocket通道 ServerSocketChannel serverChannel = ServerSocketChannel.open(); // 設置通道爲非阻塞 serverChannel.configureBlocking(false); // 將該通道對應的ServerSocket綁定到port端口 serverChannel.socket().bind(new InetSocketAddress(port)); // 得到一個通道管理器 this.selector = Selector.open(); //將通道管理器和該通道綁定,併爲該通道註冊SelectionKey.OP_ACCEPT事件,註冊該事件後, //當該事件到達時,selector.select()會返回,若是該事件沒到達selector.select()會一直阻塞。 serverChannel.register(selector, SelectionKey.OP_ACCEPT); } /** * 採用輪詢的方式監聽selector上是否有須要處理的事件,若是有,則進行處理 * @throws IOException */ @SuppressWarnings("unchecked") public void listen() throws IOException { System.out.println("服務端啓動成功!"); // 輪詢訪問selector while (true) { //當註冊的事件到達時,方法返回;不然,該方法會一直阻塞 selector.select(); // 得到selector中選中的項的迭代器,選中的項爲註冊的事件 Iterator ite = this.selector.selectedKeys().iterator(); while (ite.hasNext()) { SelectionKey key = (SelectionKey) ite.next(); // 刪除已選的key,以防重複處理 ite.remove(); // 客戶端請求鏈接事件 if (key.isAcceptable()) { ServerSocketChannel server = (ServerSocketChannel) key .channel(); // 得到和客戶端鏈接的通道 SocketChannel channel = server.accept(); // 設置成非阻塞 channel.configureBlocking(false); //在這裏能夠給客戶端發送信息哦 channel.write(ByteBuffer.wrap(new String("向客戶端發送了一條信息").getBytes())); //在和客戶端鏈接成功以後,爲了能夠接收到客戶端的信息,須要給通道設置讀的權限。 channel.register(this.selector, SelectionKey.OP_READ); // 得到了可讀的事件 } else if (key.isReadable()) { read(key); } } } } /** * 處理讀取客戶端發來的信息 的事件 * @param key * @throws IOException */ public void read(SelectionKey key) throws IOException{ // 服務器可讀取消息:獲得事件發生的Socket通道 SocketChannel channel = (SocketChannel) key.channel(); // 建立讀取的緩衝區 ByteBuffer buffer = ByteBuffer.allocate(10); channel.read(buffer); byte[] data = buffer.array(); String msg = new String(data).trim(); System.out.println("服務端收到信息:"+msg); ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes()); channel.write(outBuffer);// 將消息回送給客戶端 } /** * 啓動服務端測試 * @throws IOException */ public static void main(String[] args) throws IOException { NIOServer server = new NIOServer(); server.initServer(8000); server.listen(); } } 客戶端: package cn.nio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Iterator; /** * NIO客戶端 * */ public class NIOClient { //通道管理器 private Selector selector; /** * 得到一個Socket通道,並對該通道作一些初始化的工做 * @param ip 鏈接的服務器的ip * @param port 鏈接的服務器的端口號 * @throws IOException */ public void initClient(String ip,int port) throws IOException { // 得到一個Socket通道 SocketChannel channel = SocketChannel.open(); // 設置通道爲非阻塞 channel.configureBlocking(false); // 得到一個通道管理器 this.selector = Selector.open(); // 客戶端鏈接服務器,其實方法執行並無實現鏈接,須要在listen()方法中調 //用channel.finishConnect();才能完成鏈接 channel.connect(new InetSocketAddress(ip,port)); //將通道管理器和該通道綁定,併爲該通道註冊SelectionKey.OP_CONNECT事件。 channel.register(selector, SelectionKey.OP_CONNECT); } /** * 採用輪詢的方式監聽selector上是否有須要處理的事件,若是有,則進行處理 * @throws IOException */ @SuppressWarnings("unchecked") public void listen() throws IOException { // 輪詢訪問selector while (true) { selector.select(); // 得到selector中選中的項的迭代器 Iterator ite = this.selector.selectedKeys().iterator(); while (ite.hasNext()) { SelectionKey key = (SelectionKey) ite.next(); // 刪除已選的key,以防重複處理 ite.remove(); // 鏈接事件發生 if (key.isConnectable()) { SocketChannel channel = (SocketChannel) key .channel(); // 若是正在鏈接,則完成鏈接 if(channel.isConnectionPending()){ channel.finishConnect(); } // 設置成非阻塞 channel.configureBlocking(false); //在這裏能夠給服務端發送信息哦 channel.write(ByteBuffer.wrap(new String("向服務端發送了一條信息").getBytes())); //在和服務端鏈接成功以後,爲了能夠接收到服務端的信息,須要給通道設置讀的權限。 channel.register(this.selector, SelectionKey.OP_READ); // 得到了可讀的事件 } else if (key.isReadable()) { read(key); } } } } /** * 處理讀取服務端發來的信息 的事件 * @param key * @throws IOException */ public void read(SelectionKey key) throws IOException{ //和服務端的read方法同樣 } /** * 啓動客戶端測試 * @throws IOException */ public static void main(String[] args) throws IOException { NIOClient client = new NIOClient(); client.initClient("localhost",8000); client.listen(); } }