多線程之線程池

線程池的優勢和劣勢


優勢:減少線程的創建和銷毀,控制和調整線程的并發數

劣勢:需要根據不同的電腦設備配置不同核心線程數,根據業務設置不同的阻塞隊列的形式等;一句話:因地制宜,學習起來相對于new thread比較麻煩。

關于疑問


1 thread的創建和銷毀占用較多的資源,到底是哪些資源呢?

2 怎么使用線程池就能減少資源消耗?

3 線程池是怎么讓線程不被銷毀的?


thread 的創建是依托操作系統的,java線程的線程棧是在堆外的,不受虛擬機控制完全受操作系統控制;同時創建和銷毀需要內存的申請和回收、列入調度、在cpu進行切換時需要在內存中來回讀取信息,這樣看thread的使用確實是很復雜且比較重。

使用線程池保證了thread不是頻繁的new,有效的控制了thread的數量,在池內有可用線程時不去創建,在沒有任務執行時進行相對應的阻塞等一系列手段來減少資源的消耗。

API內的線程池


多線程之Thread?里面提到了thread的執行和工作是不分離的,而線程池的存在就行是把繁雜的工作進行有序的梳理,流程,控制(執行調度和工作單元分開)

ThreadPoolExecutor 通過名稱可以知道“線程池執行者”,這個對象就是線程的執行調度者,在它內部有兩個函數

execute(Runnable)

submit(Runnable/Callable)

而Runnable Callable 是線程的工作單元,這樣DougLea 通過把線程的執行和工作分開,讓線程更加工序化(prestartedXX,afterExecute...這一點在Android AsyncTask 的實現中更能說明這一項)

ThreadPoolExecutor是concurrent包下提供的默認實現,文章只分析ThreadPoolExecutor至于Executors里面提供的實現感興趣的可以自己學習。

打開ThreadPoolExecutor ctrl+o 先籠統的過一遍整體函數,有助于理解的就那么幾個函數而已:

1 構造函數

2 addWorker

3 beforeExecute

4 ctlOf

5 execute

6 submit

剩下都是一些get set、shutdown及線程策略(RejectedExecuteHandler的子類)?

備注:也許我個人理解的太簡單,但是我自己的學習路線確實是這么做的

構造函數


ThreadPoolExecutor(int corePoolSize,int maxPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectExecuteHandler rejectHandler)

構造函數是重載的,既然是重載的就允許使用者自我定制,且程序內的某些參數會存在默認實現。整個線程池內對于線程的管理創建銷毀等都是有以上這些參數共同決定的。

Execute 函數


execute函數內執行了3個if 判斷

execute(Runnable command){

????int c =ctl.get();? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

????if (workerCountOf(c)<corePoolSize){

? ??????????if (addWorker(command,true)) return;

????????????? ??????c =ctl.get();

????}

}

每一次只要有工作單元的對象過來都要調用workerCountOf 和corePoolSize做比較,當小于corePoolSize的時候執行的邏輯都是把“工作單元”的對象添加到works中(HashSet?workers = new HashSet()) 并執行對應“工作單元”

addWorker(Runnable task,boolean core){

? ? Work w = new Work(task);

? ? Thread t = w.thread;

? ? xxxx

? ? works.add(w);

? ? if(workedAdd)t.start();

}

到這里已經大概清晰了, 調用方創建Runnable “工作單元”,交給Executor 這個“調度者”(ThreadPoolExecutor) 通過execute進行執行(調度者里面有很多控制 工作單元的方法)

execute(Runnable command){

????int c =ctl.get();

????if (isRunning(c) &&workQueue.offer(command)){

? ??????int recheck =ctl.get();

????????if (!isRunning(recheck) && remove(command))

????????????reject(command);

? ? ? ? else if (workerCountOf(recheck) ==0)

????????????addWorker(null,false);

????}

}

上面的邏輯addWorker(null,falise)是關鍵,里面只是把 “工作單元”加入到了構造函數的 隊列里面 。

截止到目前為止已經解決了文章開頭的前兩個問題,還剩下最后一個問題"池的線程怎么不銷毀的"

在addWorker()函數內,Worker 對象綁定了一個Thread 在調用start 函數時直接觸發的是Worker---->run()

run(){

? ? runWorker(Worker);

}

runWorker(Worker){

? ? while(task != null || (task == getTask()) != null){xxx}

}

關鍵點就在于getTask這里

Runnable getTask(){

? ??????Runnable r = timed ?workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :workQueue.take();

????????if (r !=null)

????????????return r;

}

workQueue 是一個阻塞隊列,當隊內沒有任務時就會阻塞,這里看一個BlockingQueue的實現

public E take()throws InterruptedException {

????final ReentrantLock lock =this.lock;

????lock.lockInterruptibly();

????try {

? ? ? ?while (count ==0)

? ? ? ?????notEmpty.await();

????????return dequeue();

????}

}

notEmpty.await();這一行就是真正實現阻塞的原因。

到這里文章開頭的三個疑問都已經解答了。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容