慢慢說 IO 模型:改造型 BIO

小Q:前邊說了 BIO 的缺點,我們有沒有什麼方法對其改進呢?

慢慢:為了解決前面提到的 BIO 的每一個連線都需要一個執行緒處理的問題,有人提出了一個解決方案,用執行緒池的方式對執行緒進行管理,防止由於海量併發接入導致執行緒耗盡,實現系統的可控。

小Q:懂了懂了,趕快上程式碼。

服務端

public class Server { public static void main(String[] args) throws IOException { ServerSocket server = null; try { server = new ServerSocket(8080); // 建立服務端連線 ServerHandlerExecutePool executor = new ServerHandlerExecutePool(50, 10000); // 建立 I/O 任務執行緒 Socket socket = null; while (true) { socket = server。accept(); // 阻塞,直到有連線建立 executor。execute(new ServerHandler(socket)); // 線上程池中獲取一個執行緒處理連線 } } finally { if (server != null) { server。close(); server = null; } if (socket != null) { socket。close(); socket = null; } } }}

執行緒池:

public class ServerHandlerExecuePool { private ExecutorService executor; // maxPoolSize 最大執行緒數,queueSize 阻塞佇列的最大空間 public ServerHandlerExecuePool(int maxPoolSize, int queueSize) { // 初始化執行緒數,最大執行緒數,空閒執行緒存活時間,存活單位,阻塞佇列 executor = new ThreadPoolExecutor(Runtime。getRuntime()。availableProcessors(), maxPoolSize, 120L, TimeUnit。SECONDS, new ArrayBlockingQueue(queueSize)); } public execute(Runnable task) { executor。execute(task); }}

執行連線

public class ServerHandler implements Runnable { private Socket socket; public ServerHandler(Socket socket) { this。socket = socket; } @Override public void run() { BufferedReader in = null; PrintWriter out = null; try { in = new BufferedReader(new InputStreamReader(this。socket。getInputStream())); out = new PrintWriter(this。socket。getOutputStream(), true); String currentTime = null; String body = null; while (true) { body = in。readLine(); if (body == null) break; out。println(body); } } catch (Exception e) { e。printStackTrace(); } finally { if (in != null) { try { in。close(); } catch (IOException e) { e。printStackTrace(); } } if (out != null) { try { out。close(); } catch (IOException e) { e。printStackTrace(); } } } }}

客戶端

public class Client { public static void main(String[] args) { Socket socket = null; BufferReader in = null; PrintWriter out = null; try { socket = new Socket(“127。0。0。1”, 8080); // 建立連線,發起連線請求 in = new BufferReader(new InputStreamReader(socket。getInputStream())); // 監聽此緩衝區 out = new PrintWriter(socket。getOutputStream(), true); out。println(“QUERY TIME ORDER”); // 向伺服器端傳送指令 String s = in。readLine(); // 從緩衝區中獲取資料 } catch (Exception e) { e。printStackTrace(); } finally { // 優雅退出 if (out != null) { try { out。close(); // 釋放緩衝區 out = null; } catch (Exception e1) { e1。printStackTrace(); } } if (in != null) { in。close(); // 釋放緩衝區 in = null; } if (this。socket != null) { try { this。socket。close(); // 關閉連線 } catch (Exception e1) { e1。printStackTrace(); } this。socket = null; } } }}

小Q:改造後的 BIO 有什麼缺點嗎?

慢慢:改造後的 BIO 提高了執行緒數量的可控性,但還是不滿足長連線的需求場景。因為一旦建立多個長連線,執行緒池的執行緒也都被佔用,此時其他的連線只能防止阻塞佇列中。由於 TCP 連線的特性,傳送方在收不到響應時會重複傳送請求,遲遲收不到響應時會認為網路擁堵,將逐漸減少滑檔視窗的大小,直到關閉滑動視窗,使網路崩潰。