服務端執行緒模型-執行緒池服務模型
單執行緒伺服器
初學網路程式設計時,我們寫的服務端的程式碼大部分如下所示。
在一個迴圈中等待客戶端請求,一旦接到請求就在當前執行緒與客戶端進行通訊,這就是單執行緒服務模型。
這種模型有個問題,就是當請求量一上來,同時第二步的操作耗時過長時,許多請求就會阻塞在系統的Socket佇列中,無法及時得到處理,響應時間增加,嚴重會導致系統拒接請求(Socket佇列溢位),直接影響使用者體驗。
private void service() { while (true) { // 1.接到一個客戶端請求 Socket socket = serverSocket.accept(); // 2.從socket中獲取輸入輸出流,與客戶端進行通訊 InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream(); } }
多執行緒服務模型
為了應對單執行緒伺服器的缺陷,自然而然就是每來一個請求都開一個執行緒去處理,虛擬碼如下。這個模型能同時處理多個請求,每來一個請求就開一個執行緒去處理,對每個客戶都給予快速的響應不阻塞。但是不足之處也很明顯:
每來一個請求就建立一個執行緒,如果請求量大的話,建立和銷燬的執行緒就會非常多,伺服器建立和銷燬執行緒的開銷將巨增;
頻繁的建立和銷燬執行緒,會導致頻繁的上下文切換,從而影響伺服器效能;
每一個執行緒都是會佔用記憶體資源的,大量請求意味著大量的記憶體要拿去開闢這種執行緒,可能會導致系統的記憶體空間不足;
private void service() { while (true) { // 1.接到一個客戶端請求 Socket socket = serverSocket.accept(); // 2.開啟一個執行緒去處理請求 new Thread(new ServiceTask(socket)).start(); } }
執行緒池服務模型
單純的多執行緒服務模型有個問題就是開的執行緒太多,嚴重影響系統性,但是不開的話,請求處理又不及時。
有沒有辦法可以既不用開太多執行緒,又能保證請求被及時處理呢?
這時可以在服務端設定一個請求佇列,同時開適當數量的服務執行緒,這些服務執行緒不斷的從請求佇列中拿請求去進行處理,這個模型的核心思想就是通過複用執行緒,從而減少建立執行緒和銷燬執行緒帶來的系統開銷,這就是執行緒池服務模型(Master-Worker模式)。
private void service() { // 1.建立一個執行緒池 ExecutorService executorService = Executors.newFixedThreadPool(5); while (true) { // 1.接到一個客戶端請求 Socket socket = serverSocket.accept(); // 2.將請求方式請求佇列中,等待服務執行緒處理 executorService.execute(new ServiceTask(socket)); } }
使用執行緒池服務模型應該注意的點
執行緒太多:如果執行緒數開的太多,這些執行緒會消耗包括記憶體在內的其他系統資源,影響系統性能。所以應當根據具體情況開合適的執行緒數
請求太多:請求太多意味著請求佇列過大,同樣也會佔用過多的系統資源。所以應當設定適當的拒絕策略,保護系統的同時不過與影響使用者體驗
執行緒洩漏:如果服務執行緒阻塞了,比如等待使用者請求,比如死鎖。這個服務執行緒就無法去處理請求,執行緒池將會失去這個“勞動力”,如果接二連三所有的執行緒都因為某個原因而無法處理請求,執行緒池終將沒有剩餘的工作執行緒去處理佇列中的請求,這就是執行緒洩漏,所以,應當在阻塞的地方設定合理的最大阻塞時間避免永久的阻塞,同時避免死鎖。
引用
1.《Java網路程式設計精解》(孫衛琴)