深入研究線程池
一.什麼是線程池?
線程池就是以一個或多個線程[迴圈執行]多個應用邏輯的線程集合.
注意這裡用了線程集合的概念是我生造的,目的是為了區分執行一批應用邏輯的多個線程和
線程組的區別.關於線程組的概念請參閱基礎部分.
一般而言,線程池有以下幾個部分:
1.完成主要任務的一個或多個線程.
2.用於調度管理的管理線程.
3.要求執行的任務隊列.
那麼如果一個線程迴圈執行一段代碼是否是線程池?
如果極端而言,應該算,但實際上迴圈代碼應該算上一個邏輯單元.我們說最最弱化的線程池
應該是迴圈執行多個邏輯單元.也就是有一批要執行的任務,這些任務被獨立為多個不同的執行
單元.比如:
int x = 0;
while(true){
x ++;
}
這就不能說迴圈中執行多個邏輯單元,因為它只是簡單地對迴圈外部的初始變數執行++操作.
而如果已經有一個隊列
ArrayList al = new ArrayList();
for(int i=0;i<10000;i++){
al.add(new AClass());
}
然後在一個線程中執行:
while(al.size() != 0){
AClass a = (AClass)al.remove(0);
a.businessMethod();
}
我們說這個線程就是迴圈執行多個邏輯單元.可以說這個線程是弱化的線程池.我們習慣上把這些
相對獨立的邏輯單元稱為任務.
二.為什麼要建立線程池?
線程池屬於對象池.所有對象池都具有一個非常重要的共性,就是為了最大程度複用對象.那麼
線程池的最重要的特徵也就是最大程度利用線程.
從編程模型模型上說講,在處理多任務時,每個任務一個線程是非常好的模型.如果確實可以這麼
做我們將可以使用編程模型更清楚,更最佳化.但是在實際應用中,每個任務一個線程會使用系統限
入"過度切換"和"過度開銷"的泥潭.
打個比方,如果可能,生活中每個人一輛房車,上面有休息,娛樂,餐飲等生活措施.而且道路交道永遠
不堵車,那是多麼美好的夢中王國啊.可是殘酷的現實告訴我們,那是不可能的.不僅每個人一輛車
需要無數多的社會資源,而且地球上所能容納的車輛總數是有限制的.
首先,建立線程本身需要額外(相對於執行任務而必須的資源)的開銷.
作業系統在每建立一個線程時,至少需要建立以下資源:
線程核心對象用於對線程內容相關的管理.
使用者模式執行棧.
核心模式執行棧.
這些資源被線程佔有後作業系統和使用者都無法使用.
相反的過程,銷毀線程需要回收資源,也需要一定開銷.
其次,過多的線程將導致過度的切換.
線程切換帶來的效能更是不可估量.系統完成線程切換要經過以下過程:
從使用者模式切換到核心模式.
將CPU寄存器的值儲存到當前線程的核心對象中.
開啟一個自旋鎖,根據調度策略決定下一個要執行的線程.釋放自旋鎖,如果要執行的線程不是同一
進程中的線程,還需要切換虛擬記憶體等進程環境.
將要執行的線程的核心對象的值寫到CPU寄存器中.
切換到使用者模式執行新線程的執行邏輯.
以上開銷對於使用者要執行的任務而言,都是額外的.更不可容忍的是,如果使用者的任務邏輯都是很小
的單元,而新分配線程和線程切換的開銷與任務邏輯需要的開銷的比例可能會10:1,100:1,1000:1.
也就是你花了1000$買的衣服只穿了一天!
所以線程池的目的就是為了減少建立和切換線程的額外開銷,利用已經的線程多次迴圈執行多個任
務從而提高系統的處理能力.也就是在"社會主義初級階段"一件衣服應該盡量多穿一些天數.
[擴充知識]
儘管目前絕大多數JVM實現都是一個Java線程對應一個作業系統線程,但事實上(如果是我來實現JVM)
完全可以用一個作業系統線程執行多個Java線程,因為對於作業系統線程來說Java線程就是一個任務.
而且無論是作業系統線程或Java線程中都可以更細地劃分為超細線程(纖程),即線上程內部實現對纖
程的調度利用纖程來執行任務.
三.如何?線程池?
一個線程池至少應該具有以下幾個方面的功能:
1.提供一個任務介面以便使用者加入任務
由於Java不支援方法指標,所以操作(方法)只能綁定在對象上,將擁有操作的隊象放入隊列中,以便
背景工作執行緒能按一定策略擷取對象然後執行其上的方法.
這裡需要有兩個組件,一是規定操作的任務介面:
interface ITask{
public void task();
}
一是存放操作的容器.容器可以根據調度策略來選擇或自己實現,比如一個最簡單的FIFO策略的容器
可以用LinkedList來實現.需要注意的是對這個容器的存取需要同步:
class TaskList{
private LinkedList<ITask> tl = new LinkedList();
public synchronized void addTask(ITask task){
this.tl.add(task);
//notifyAll();
}
//加上addFistTask和addLastTask
public synchronized ITask getTask(){
if(this.size() <= 0)
wait();
return this.tl.poll();
}
//加上getFistTask和getLastTask
public synchronized int getCount(){
return this.tl.size();
}
public synchronized void removeAll(){
this.tl.clear();
}
}
加上addFistTask和addLastTask/getFistTask和getLastTask實現就可以實現簡單的優先順序調度.
2.背景工作執行緒
我們把用來執行使用者任務的線程稱為背景工作執行緒,背景工作執行緒就是不斷從隊列中擷取任務對象並執行對象
上的業務方法:
class WorkThread extends Thread{
private TaskList list;
//多個背景工作執行緒共同從一個任務隊列中擷取任務,所以要從外面傳入一個任務隊列.
public WorkThread(String name,TaskList list){
super(name);
this.list = list;
}
public void run(){
while(true){
ITask task = null;
task = list.getTask();
if(task != null) task.task();
}
}
}
在基礎知識部分已經介紹了對線程狀態的標記控制,請參照前面的知識自己加入對線線程狀態的判斷.
完成了這幾個基礎部分,就需要有一個對背景工作執行緒的調度線程.完成以下幾個功能:
1.產生需要的背景工作執行緒.由於建立線程需要一定的開銷,一定要注意所建立的所有線程不能超一個設定
的最大值.建議最大值不要超25.
2.動態自適應調整集合中線程數.當有太多的線程處於閑置狀態時(隊列中沒有任務),應該按一定比例
銷毀閑置了一定時的線程.如果隊列中任務隊列積壓太多而背景工作執行緒總數沒有超最大線程數時應該及時
建立背景工作執行緒直至達到是大值.
3.需要一個專門的後台線程定時掃描隊列中任務與正在工作的線程總數,閑置的線程總數.
以上功能有不同最佳化方法實現,可以參考JDK的線程池實現.