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

1、問題

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

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

2、BIO 代碼實現

  1. publicclassDemoServerextendsThread{
  2. privateServerSocket serverSocket;
  3. publicint getPort(){
  4. return serverSocket.getLocalPort();
  5. }
  6. publicvoid run(){
  7. try{
  8. serverSocket =newServerSocket(0);
  9. while(true){
  10. Socket socket = serverSocket.accept();
  11. RequestHandler requestHandler =newRequestHandler(socket);
  12. requestHandler.start();
  13. }
  14. }catch(IOException e){
  15. e.printStackTrace();
  16. }finally{
  17. if(serverSocket !=null){
  18. try{
  19. serverSocket.close();
  20. }catch(IOException e){
  21. e.printStackTrace();
  22. }
  23. }
  24. }
  25. }
  26. publicstaticvoid main(String[] args)throwsIOException{
  27. DemoServer server =newDemoServer();
  28. server.start();
  29. try(Socket client =newSocket(InetAddress.getLocalHost(), server.getPort())){
  30. BufferedReader bufferedReader =newBufferedReader(newInputStreamReader(client.getInputStream()));
  31. bufferedReader.lines().forEach(s ->System.out.println(s));
  32. }
  33. }
  34. }
  35. // 簡化實現,不作讀取,直接發送字符串
  36. classRequestHandlerextendsThread{
  37. privateSocket socket;
  38. RequestHandler(Socket socket){
  39. this.socket = socket;
  40. }
  41. @Override
  42. publicvoid run(){
  43. try(PrintWriter out =newPrintWriter(socket.getOutputStream());){
  44. out.println("Hello world!");
  45. out.flush();
  46. }catch(Exception e){
  47. e.printStackTrace();
  48. }
  49. }
  50. }

 

  • 服務器端啓動 ServerSocket,端口 0 表示自動綁定一個空閒端口。
  • 調用 accept 方法,阻塞等待客戶端鏈接。
  • 利用 Socket 模擬了一個簡單的客戶端,只進行鏈接、讀取、打印。
  • 當鏈接創建後,啓動一個單獨線程負責回覆客戶端請求。

這樣,一個簡單的 Socket 服務器就被實現出來了。併發

 

 

(圖片來源於楊曉峯)機器學習

3、NIO 代碼實現

  1. publicclassNIOServerextendsThread{
  2. publicvoid run(){
  3. try(Selector selector =Selector.open();
  4. ServerSocketChannel serverSocket =ServerSocketChannel.open();){// 建立 Selector 和 Channel
  5. serverSocket.bind(newInetSocketAddress(InetAddress.getLocalHost(),8888));
  6. serverSocket.configureBlocking(false);
  7. // 註冊到 Selector,並說明關注點
  8. serverSocket.register(selector,SelectionKey.OP_ACCEPT);
  9. while(true){
  10. selector.select();// 阻塞等待就緒的 Channel,這是關鍵點之一
  11. Set<SelectionKey> selectedKeys = selector.selectedKeys();
  12. Iterator<SelectionKey> iter = selectedKeys.iterator();
  13. while(iter.hasNext()){
  14. SelectionKey key = iter.next();
  15. // 生產系統中通常會額外進行就緒狀態檢查
  16. sayHelloWorld((ServerSocketChannel) key.channel());
  17. iter.remove();
  18. }
  19. }
  20. }catch(IOException e){
  21. e.printStackTrace();
  22. }
  23. }
  24. privatevoid sayHelloWorld(ServerSocketChannel server)throwsIOException{
  25. try(SocketChannel client = server.accept();){ client.write(Charset.defaultCharset().encode("Hello world!"));
  26. }
  27. }
  28. // 省略了與前面相似的 main
  29. }

 

  • 首先,經過 Selector.open() 建立一個 Selector,做爲相似調度員的角色。
  • 而後,建立一個 ServerSocketChannel,而且向 Selector 註冊,經過指定 SelectionKey.OP_ACCEPT,告訴調度員,它關注的是新的鏈接請求。注意:爲何咱們要明確配置非阻塞模式呢?這是由於阻塞模式下,註冊操做是不容許的,會拋出 IllegalBlockingModeException 異常。
  • Selector 阻塞在 select 操做,當有 Channel 發生接入請求,就會被喚醒。
  • 在 sayHelloWorld 方法中,經過 SocketChannel 和 Buffer 進行數據操做,在本例中是發送了一段字符串。

能夠看到,在前面兩個樣例中,IO 都是同步阻塞模式,因此須要多線程以實現多任務處理。而 NIO 則是利用了單線程輪詢事件的機制,經過高效地定位就緒的 Channel,來決定作什麼,僅僅 select 階段是阻塞的,能夠有效避免大量客戶端鏈接時,頻繁線程切換帶來的問題,應用的擴展能力有了很是大的提升。下面這張圖對這種實現思路進行了形象地說明。socket

 

 

做者: 王磊的博客分佈式

免費Java資料領取,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo/Kafka、Hadoop、Hbase、Flink等高併發分佈式、大數據、機器學習等技術。
傳送門: https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q
相關文章
相關標籤/搜索