阿里面試題BIO和NIO數量問題附答案和代碼

1、問題

BIO 和 NIO 做爲 Server 端,當創建了 10 個鏈接時,分別產生多少個線程?html

答案: 由於傳統的 IO 也就是 BIO 是同步線程堵塞的,因此每一個鏈接都要分配一個專用線程來處理請求,這樣 10 個鏈接就會建立 10 個線程去處理。而 NIO 是一種同步非阻塞的 I/O 模型,它的核心技術是多路複用,可使用一個連接上的不一樣通道來處理不一樣的請求,因此即便有 10 個鏈接,對於 NIO 來講,開啓 1 個線程就夠了。java

2、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 服務器就被實現出來了。面試

(圖片來源於楊曉峯)服務器

3、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<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> 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 階段是阻塞的,能夠有效避免大量客戶端鏈接時,頻繁線程切換帶來的問題,應用的擴展能力有了很是大的提升。下面這張圖對這種實現思路進行了形象地說明。多線程

(圖片來源於楊曉峯)socket

4、參考資料

Java核心36講ide

公衆號二維碼

近期熱門文章:this

Java 最多見的 200+ 面試題spa

相關文章
相關標籤/搜索