爲了解決同步阻塞I/O面臨的一個鏈路須要一個線程處理的問題,後來有人對它的線程模型進行了優化,後端經過一個線程池來處理多個客戶端的請求接入,造成客戶端個數M:線程池最大線程數N的比例關係,其中M能夠遠遠大於N,經過線程池能夠靈活的調配線程資源,設置線程的最大值,防止因爲海量併發接入致使線程耗盡。前端
僞異步I/O模型圖java
採用線程池和任務隊列能夠實現一種叫作僞異步的I/O通訊框架,它的模型圖以下:後端
當有新的客戶端接入的時候,將客戶端的Socket封裝成一個Task(該任務實現java.lang.Runnable接口)投遞到後端的線程池中進行處理,JDK的線程池維護一個消息隊列和N個活躍線程對消息隊列中的任務進行處理。因爲線程池能夠設置消息隊列的大小和最大線程數,所以,它的資源佔用是可控的,不管多少個客戶端併發訪問,都不會致使資源的耗盡和宕機。服務器
僞異步I/O的主函數代碼發生了變化,咱們首先建立一個時間服務器處理類的線程池,當接收到新的客戶端鏈接的時候,將請求Socket封裝成一個Task,而後調用線程池的execute方法執行,從而避免了每一個請求接入都建立一個新的線程。網絡
當對Socket的輸入流進行讀取操做的時候,它會一直阻塞下支,直到發生以下三種事件:併發
有數據可讀;框架
可用數據已經讀取完畢;異步
發生空指針或者I/O異常;函數
這意味着當對方發送請求或者應答消息比較緩慢,或者網絡傳輸比較慢時,讀取輸入流一方的通訊線程將被長時間阻塞,若是對方要60s纔可以將數據發送完成,讀取一方的I/O線程也將會被同步阻塞60s,在此期間,其餘接入消息只能在消息隊列中排隊。學習
當調用OutputStream的write方法寫輸出流的時候,它將會被阻塞,直到全部要發送的字節所有寫入完畢,或者發生異常。學習過TCP/IP相關知識的人都知道,當消息的接收方處理緩慢的時候,將不能及時地從TCP緩衝區讀取數據,這將會致使發送方的TCP window size不斷減少,直到爲0,雙方處於Keep-Alive狀態,消息發送方將不能再向TCP緩衝區寫入消息,這時若是採用的是同步阻塞I/O,write操做將會被無限期阻塞,直到TCP window size大於0或者發生I/O異常。
經過對輸入和輸出流的API文檔進行分析,咱們瞭解到讀和寫操做都是同步阻塞的,阻塞的時間取決於對方I/O線程的處理速度和網絡I/O的傳輸速度。
僞異步I/O實際上僅僅只是對以前I/O線程模型的一個簡單優化,它沒法從根本上解決同步I/O致使的通訊線程阻塞問題。下面咱們就簡單分析下若是通訊對方返回應答時間過長,會引發的級聯故障:
服務端處理緩慢,返回應答消息耗費60s,平時只須要10s。
採用僞異步I/O的線程正在讀取故障服務節點的響應,因爲讀取輸入流是阻塞的,所以,它將會被同步阻塞60s。
假如全部的可用線程都被故障服務器阻塞,那後續全部的I/O消息都將在隊列中排隊。
因爲線程池採用阻塞隊列實現,當隊列積滿以後,後續入隊列的操做將被阻塞。
因爲前端只有一個Accptor線程接收客戶端接入,它被阻塞在線程池的同步阻塞隊列以後,新的客戶端請求消息將被拒絕,客戶端會發生大量的鏈接超時。
因爲幾乎全部的鏈接都超時,調用者會認爲系統已經崩潰,沒法接收新的請求消息。