一、問題
BIO 和 NIO 作為 Server 端,當建立了 10 個連線時,分別產生多少個執行緒?
答案: 因為傳統的 IO 也就是 BIO 是同步執行緒堵塞的,所以每個連線都要分配一個專用執行緒來處理請求,這樣 10 個連線就會建立 10 個執行緒去處理。而 NIO 是一種同步非阻塞的 I/O 模型,它的核心技術是多路複用,可以使用一個連結上的不同通道來處理不同的請求,所以即使有 10 個連線,對於 NIO 來說,開啟 1 個執行緒就夠了。
二、BIO 程式碼實現
public class DemoServer extends Thread { private ServerSocket serverSocket; public int getPort() { return serverSocket.getLocalPort(); } public void run() { try { serverSocket = new ServerSocket(0); while (true) { Socket socket = serverSocket.accept(); RequestHandler requestHandler = new RequestHandler(socket); requestHandler.start(); } } catch (IOException e) { e.printStackTrace(); } finally { if (serverSocket != null) { try { serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } } public static void main(String[] args) throws IOException { DemoServer server = new DemoServer(); server.start(); try (Socket client = new Socket(InetAddress.getLocalHost(), server.getPort())) { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(client.getInputStream())); bufferedReader.lines().forEach(s -> System.out.println(s)); } } } // 簡化實現,不做讀取,直接傳送字串 class RequestHandler extends Thread { private Socket socket; RequestHandler(Socket socket) { this.socket = socket; } @Override public void run() { try (PrintWriter out = new PrintWriter(socket.getOutputStream());) { out.println("Hello world!"); out.flush(); } catch (Exception e) { e.printStackTrace(); } } } 複製程式碼 伺服器端啟動 ServerSocket,埠 0 表示自動繫結一個空閒埠。 呼叫 accept 方法,阻塞等待客戶端連線。 利用 Socket 模擬了一個簡單的客戶端,只進行連線、讀取、列印。 當連線建立後,啟動一個單獨執行緒負責回覆客戶端請求。 這樣,一個簡單的 Socket 伺服器就被實現出來了。
三、NIO 程式碼實現
public class NIOServer extends Thread { public void run() { try (Selector selector = Selector.open(); ServerSocketChannel serverSocket = ServerSocketChannel.open();) {// 建立 Selector 和 Channel serverSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(), 8888)); serverSocket.configureBlocking(false); // 註冊到 Selector,並說明關注點 serverSocket.register(selector, SelectionKey.OP_ACCEPT); while (true) { selector.select();// 阻塞等待就緒的 Channel,這是關鍵點之一 Set selectedKeys = selector.selectedKeys(); Iterator iter = selectedKeys.iterator(); while (iter.hasNext()) { SelectionKey key = iter.next(); // 生產系統中一般會額外進行就緒狀態檢查 sayHelloWorld((ServerSocketChannel) key.channel()); iter.remove(); } } } catch (IOException e) { e.printStackTrace(); } } private void sayHelloWorld(ServerSocketChannel server) throws IOException { try (SocketChannel client = server.accept();) { client.write(Charset.defaultCharset().encode("Hello world!")); } } // 省略了與前面類似的 main } 複製程式碼 首先,通過 Selector.open() 建立一個 Selector,作為類似排程員的角色。 然後,建立一個 ServerSocketChannel,並且向 Selector 註冊,通過指定 SelectionKey.OP_ACCEPT,告訴排程員,它關注的是新的連線請求。注意:為什麼我們要明確配置非阻塞模式呢?這是因為阻塞模式下,註冊操作是不允許的,會丟擲 IllegalBlockingModeException 異常。 Selector 阻塞在 select 操作,當有 Channel 發生接入請求,就會被喚醒。 在 sayHelloWorld 方法中,通過 SocketChannel 和 Buffer 進行資料操作,在本例中是傳送了一段字串。 可以看到,在前面兩個樣例中,IO 都是同步阻塞模式,所以需要多執行緒以實現多工處理。而 NIO 則是利用了單執行緒輪詢事件的機制,通過高效地定位就緒的 Channel,來決定做什麼,僅僅 select 階段是阻塞的,可以有效避免大量客戶端連線時,頻繁執行緒切換帶來的問題,應用的擴充套件能力有了非常大的提高。下面這張圖對這種實現思路進行了形象地說明。
歡迎工作一到五年的Java工程師朋友們加入Java架構師:697558955
群內提供免費的Java架構學習資料(裡面有高可用、高併發、高效能及分散式、Jvm效能調優、Spring原始碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!