直接創建大量線程的壞處
對於一個web服務器,服務器應用程序會處理來自客戶端的請求。假設,每到達一個請求,我們的程序都為該請求創建一個線程來執行請求任務,那么這個創建的線程數目將會是無窮無盡的,“為每一個請求任務分配一個線程”,該做法是存在一些缺陷的,尤其是創建大量線程時:
(1)線程的生命周期的開銷高:我們要明白線程的創建和銷毀是需要代價的,如果說客戶端請求的任務是很輕量級的,往往這種任務所執行的時間都很短並且做的事也很簡單,那么為每一個請求創建線程,會消耗大量的計算機資源的。
(2)資源的消耗:一個活躍的線程會消耗系統的資源,特別是內存。當創建的線程太多時並且多過了CPU的核心數,必定會有線程會閑置。當大量線程閑置時,會占用大量的內存(極有可能會造成OOM異常),而且這么多的線程之間也會因為CPU的不足而存在競爭(線程經常得在用戶態和內核態之間轉換需要開銷),所以當你的線程數如果達到了剛好使CPU處於忙碌,那么這時繼續創建新線程,反而會降低性能。
線程池的作用
線程池作用就是限制系統中執行線程的數量。
根據系統的環境情況,可以自動或手動設置線程數量,達到運行的最佳效果;少了浪費了系統資源,多了造成系統擁擠效率不高。用線程池控制線程數量,其他線程排隊等候。一個任務執行完畢,再從隊列的中取最前面的任務開始執行。若隊列中沒有等待進程,線程池的這一資源處於等待。當一個新任務需要運行時,如果線程池中有等待的工作線程,就可以開始運行了;否則進入等待隊列。
為什么要用線程池:
(1)減少了創建和銷毀線程的次數,每個工作線程都可以被重復利用,可執行多個任務。
(2)可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因為消耗過多的內存,而把服務器累趴下(每個線程需要大約1MB內存,線程開的越多,消耗的內存也就越大,最后死機)。
那么在創建線程池時,我們又該如何設置好線程池的大小呢?
首先,設置一個線程池之前,我們先要知道線程所執行的任務,都是有不同的執行策略的:
(1)依賴性任務:大多數的任務都是不依賴於其他任務所執行的時序、時長、執行結果,但是如果一個任務執行需要依賴於其他的任務,那么該任務可以被認為是有依賴性的。
(2)線程封閉的任務:該任務在執行時,其他任務都不能執行,適合單線程池。
(3)響應時間敏感的任務:在上面的線程封閉任務,如果一個執行時間很長的任務交給單線程池去做,那么很長時間才會得到反饋,,所以時間敏感的任務不適合單線程模式。
對於依賴性很強的任務,要時刻注意線程池里的線程數目不能過小,因為線程數如果不夠,A任務所依賴的B任務可能就要被放入等待隊列,那么此時A任務就很可能執行不下去,最極端的情況可能就是B此時正好需要A任務所占用的線程,這時就會導致“線程飢餓死鎖”。
那么如何設置線程池大小?
性質不同的任務可以交給不同規模的線程池執行。
任務的性質:
(1)CPU密集型
(2)I/O密集型
(3)混合型任務
對於不同性質的任務來說,CPU密集型任務應配置盡可能小的線程,因為如果為CPU密集型的任務開啟太多的線程,那么CPU為了調度好這些線程會消耗太多資源。而IO密集型任務應配置盡可能多的線程,因為IO操作不占用CPU,所以盡管線程比較多,CPU也不用為了調度線程而花費太多開銷,所以可以加大線程數量(但是也別太多)。
若任務對其他系統資源有依賴,如某個任務依賴數據庫的連接返回的結果,這時候等待的時間越長,則CPU空閑的時間越長,那么線程數量應設置得越大,這樣CPU就會去處理其他的線程,所以才能更好的利用CPU。
當然具體合理線程池值大小,需要結合系統實際情況,在大量的嘗試下比較才能得出,以上只是前人總結的規律。
最佳線程數目 = ((線程等待時間+線程CPU時間)/線程CPU時間 )* CPU數目
比如平均每個線程CPU運行時間為0.5s,而線程等待時間(非CPU運行時間,比如IO)為1.5s,CPU核心數為8,那么根據上面這個公式估算得到:((0.5+1.5)/0.5)*8=32。這個公式進一步轉化為:
最佳線程數目 = (線程等待時間與線程CPU時間之比 + 1)* CPU數目
總結:
線程等待時間所占比例越高(線程此時很可能在等待依賴性任務完成或者處於IO阻塞,此時不占CPU,所以應該創建更多的線程讓空閑的CPU去處理),需要越多線程。線程CPU時間所占比例越高,需要越少線程。
高並發、任務執行時間短的業務怎樣使用線程池?並發不高、任務執行時間長的業務怎樣使用線程池?並發高、業務執行時間長的業務怎樣使用線程池?
(1)高並發、任務執行時間短的業務:因為時間短,很可能就是CPU密集型,所以線程不宜過多,和CPU核心數差不多就好。
(2)並發不高、任務執行時間長的業務要區分開看:
a)假如是業務時間長集中在IO操作上,也就是IO密集型的任務,因為IO操作並不占用CPU,所以不要讓所有的CPU閑下來,可以適當加大線程池中的線程數目,讓CPU處理更多的業務 。
b)假如是業務時間長集中在計算操作上,也就是計算密集型任務,這個就沒辦法了,和(1)一樣吧,線程池中的線程數設置得少一些,減少線程上下文的切換 。
(3)並發高、任務執行時間又長,解決這種類型任務的關鍵不在於線程池而在於整體架構的設計,看看這些業務里面某些數據是否能做緩存是第一步,增加服務器是第二步,至於線程池的設置,設置參考(2)。最后,業務執行時間長的問題,也可能需要分析一下,看看能不能使用中間件對任務進行拆分和解耦。