1、使用線程池的好處
線程使應用能夠更加充分合理地協調利用CPU、內存、網絡、I/O等系統資源。線程的創建需要開闢虛擬機棧、程序計數器、本地方法棧等線程私有的內存空間。在線程銷燬時需要回收這些資源。頻繁地創建和銷燬線程會浪費大量的系統資源,增加併發編程風險。另外,在服務器負載過大的時候,如何讓新線程等待或者友好地拒絕服務?這都是線程自身無法解決的。所以需要通過線程池協調多個線程,並實現類似主次線程隔離、定時執行、週期執行等任務。線程池的作用包括:
- 利用線程池管理並複用線程、控制最大併發數。
- 實現任務線程隊列緩存策略和拒絕機制。
- 實現某些與實踐相關的功能,如定時執行、週期執行等。
- 隔離線程環境。比如,交易服務和搜索服務在同一臺服務器上,分別開啓兩個線程池,交易線程的資源消耗明顯要大;因此,通過配置獨立的線程池,將較慢的交易服務與搜索服務隔離開,避免各服務線程相互影響。
2、ThreadPoolExecutor
瞭解線程池的基本作用後,我們學習一下線程池是如何創建線程的。首先從ThreadPoolExecutor構造方法講起,學習如何自定義ThreadFactory和RejectedExecutionHandler,並編寫一個最簡單的線程池示例。然後通過分析ThreadPoolExecutor的execute和addWorker兩個核心方法,學習如何把任務線程加入到線程池中運行。
ThreadPoolExecutor構造方法如下:
public ThreadPoolExecutor(
int corePoolSize, //參數1
int maximumPoolSize, //參數2
long keepAliveTime, //參數3
TimeUnit unit, //參數4
BlockingQueue<Runnable> workQueue, //參數5
ThreadFactory threadFactory, //參數6
RejectedExecutionHandler handler) { //參數7
if (corePoolSize < 0 ||
//maximumPoolSize必須大於或等於1,也要大於或等於corePoolSize(第一處)
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
//(第二處)
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
- 參數1,corePoolSize:表示常駐核心線程數。如果等於0,則任務執行完成以後,沒有任何請求進入時銷燬線程池的線程;如果大於0,即使本地任務執行完畢,核心線程也不會被銷燬。這個值的設置非常關鍵,設置過大會浪費資源,設置過小會導致線程頻繁地創建或銷燬。
- 參數2,maximumPoolSize:表示線程池能夠容納同時執行的最大線程數。從上方示例代碼中的第一處來看,必須大於或等於1.如果待執行的線程數大於此值,需要藉助第五個參數的幫助,緩存在隊列中。如果maximumPoolSize與corePoolSize相等,即是固定大小線程池。
- 參數3,keepAliveTime:表示線程池中線程空閒時間,當空閒時間達到keepAliveTime值時,線程會被銷燬,直到只剩下corePoolSize個線程爲止,避免浪費內存和句柄資源。在默認情況下,當線程池的線程數大於corePoolSize時,keepAliveTime纔會起作用。但是當ThreadPoolExecutor的allowCoreThreadTimeOut變量設置爲true時,核心線程超時後也會被回收。
- 參數4,TimeUnit:表示時間單位。KeepAliveTime的時間單位通常是TimeUnit.SECONDS。
- 參數5,workQueue:表示緩存隊列。當請求的線程數大於corePoolSize時,線程進入BlockingQueue阻塞隊列(請注意,是當corePoolSize不夠用時,將任務加入緩存隊列,當緩存隊列也容納不下任務時,再開闢新的線程來處理任務,直到線程數到達maximumPoolSize)。後續示例代碼中使用的LinkedBlockingQueue是單向鏈表,使用鎖來控制入隊和出隊的原子性,兩個鎖分別控制元素的添加和獲取,是一個生產消費模型隊列。
- 參數6,threadFactory表示線程工廠。它用來生產一組相同任務的線程。線程池的命名是通過給這個factory增加組名前綴來實現的。在虛擬機棧分析時,就可以知道線程任務是由哪個線程工廠產生的。
- 參數7,handler表示執行拒絕策略的對象。當第五個參數workQueue的任務緩存區到達上限後,並且活動線程數等於maximumPoolSize的時候,線程池通過該策略處理請求,這是一種簡單的限流保護。友好地拒絕策略可以是如下三種:保存到數據庫進行削峯填谷,在空閒時再提取出來執行、轉向某個提示頁面、打印日誌。
從代碼中的第二處來看,隊列、線程工廠、拒絕處理服務都必須有示例對象,但在實際編程中,很少有程序員對這三者進行實例化,而是通過Executors這個線程池靜態工廠提供默認實現,那麼Executors與ThreadPoolExecutor是什麼關係呢?(從源碼上看,ThreadPoolExecutor是Executors的底層實現)線程池相關類圖如下所示:
ExecutorService接口繼承了Executor接口,定義了管理線程任務的方法。ExecutorService的抽象類AbstractExecutorService提供了submit()、invokeAll()等部分方法的實現。但是核心方法Executor.execute()並沒有在這裏實現。因爲所有的任務都在這個方法裏執行,不同實現會帶來不同的執行策略。通過Executor的靜態工廠方法可以創建三個線程池的包裝對象:ForkJoinPool、ThreadPoolExecutor、ScheduledThreadPoolExecutor。
Executors(Java提供用來創建線程池的類,在JUC包下)的核心方法有五個(這五個方法每個都有多種重載方法,我只選取了一種):
- Executor.newWorkStealingPool:JDK8引入,創建持有足夠線程的線程池支持給定的並行度,並通過使用多個隊列減少競爭,此構造方法中把CPU數量設置爲默認的並行度。
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory); }
- Executors.newCachedThreadPool:maximumPoolSize最大可以至Integer.MAX_VALUE,是高度可伸縮的線程池,如果達到這個上限,相信沒有任何服務器能夠繼續工作,肯定會拋出OOM異常。keepAliveTime默認60秒,工作線程處於空閒狀態,則回收工作線程。如果任務數增加,再次創建出新線程處理任務。
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory); }
- Executors.newScheduledThreadPool:線程數最大值Integer.MAX_VALUE,與上述相同,存在OOM風險。它是ScheduledExecutorService接口家族的實現類,支持定時及週期性任務執行。相比Timer,ScheduledExecutorService更安全,功能更加強大,與newCachedThreadPool的區別是不回收工作線程。
public static ScheduledExecutorService newScheduledThreadPool( int corePoolSize, ThreadFactory threadFactory) { return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory); }
- Executors.newSingleThreadExecutor:創建一個單線程的線程池,相當於單線程串行執行所有任務,保證按任務的提交順序依次執行。
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory)); }
- Executors.newFixedThreadPool:輸入的參數即是固定線程數,即是核心線程數也是最大線程數,不存在空閒線程,所以keepAliveTime等於0:
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } //這裏輸入的隊列沒有指明長度,看一下LinkedBlockingQueue的構造方法 public LinkedBlockingQueue(){ this(Integer.MAX_VALUE); } //使用這樣的無界隊列,如果瞬間請求量很大的話,會有OOM風險
除了newWorkStealingPool外,其他四個創建方式都存在資源耗盡的風險。
Executors中默認的線程工廠(threadFactory)和拒絕策略(handler)過於簡單,通常對用戶來說都不太友好,線程工廠需要做創建前的準備工作,對線程池創建的線程必須明確標識,就像藥品的生產批號一樣,爲線程本身指定有意義的名稱和相應的序列號。拒絕策略應該考慮到業務場景,返回相應的提示或者友好地跳轉,以下爲簡單的ThreadFactory示例:
package Test;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
public class UserThreadFactory implements ThreadFactory {
//name prefix譯爲名稱前綴
private final String namePrefix;
//AtomicInteger是一個原子操作類,能夠保證操作的原子性
private final AtomicInteger nextId = new AtomicInteger();
//定義線程組名稱whatFeatureOfGroup,在使用jstack來排查線程問題時,非常有幫助
UserThreadFactory(String whatFeatureOfGroup){
this.namePrefix = "UserThreadFactory's" + whatFeatureOfGroup + "-Worker-";
}
@Override
public Thread newThread(Runnable task) {
String name = namePrefix + nextId.getAndIncrement();
Thread thread = new Thread(null, task, name, 0, false);
System.out.println(thread.getName());
return thread;
}
}
//任務執行體
class Task implements Runnable{
private final AtomicLong count = new AtomicLong(0L);
@Override
public void run() {
System.out.println("running_" + count.getAndIncrement());
}
}
這個示例包括線程工廠和任務執行體的定義,通過newThread方法快速、統一地創建線程任務,強調線程一定要有特定意義的名稱,方便出錯時回溯。
簡單地實現一下RejectedExecutionHandler,實現了接口的rejectedExecution方法,打印出當前線程池狀態:
public class UserRejectHandler implements RejectedExecutionHandler{
@Override
public void rejectedExecution(Runnable task, ThreadPoolExecutor executor){
System.out.println("task rejected." + executor.toString());
}
}
當一個任務Task欲通過threadPoolexecutor.execute(Runnable)添加到線程中時,會出現以下幾種情況:
- 此時線程池中的線程數小於corePoolSize(可以通過設置參數讓核心線程也空閒超時銷燬)時,即使線程池中的線程都處於空閒狀態,那麼也要創建一個新的線程來執行這個任務Task;
- 此時線程池中線程數量等於corePoolSize,但是緩衝隊列workQueue未滿,則將任務加入緩存隊列等待處理;
- 此時線程池中線程數量大於corePoolSize,緩衝隊列workQueue已滿,但是線程數量小於maximumPoolSize時,則新建一個線程來執行此任務Task;
- 此時線程池中線程數量大於corePoolSize,緩衝隊列workQueue已滿,且線程數量等於maximumPoolSize時,那麼通過指定的handler來對該任務Task執行拒絕策略。
handler有四個選擇,在ThreadPoolExecutor中提供了四個公開的靜態內部類:
- AbortPolicy(默認):丟棄任務並拋出RejectedExecutionException異常。
public static class AbortPolicy implements RejectedExecutionHandler { /** * Creates an {@code AbortPolicy}. */ public AbortPolicy() { } /** * Always throws RejectedExecutionException. * * @param r the runnable task requested to be executed * @param e the executor attempting to execute this task * @throws RejectedExecutionException always. */ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString()); } }
- DiscardPolicy:丟棄任務,但不拋出異常,這是不推薦的做法。
public static class DiscardPolicy implements RejectedExecutionHandler { /** * Creates a {@code DiscardPolicy}. */ public DiscardPolicy() { } /** * Does nothing, which has the effect of discarding task r. * * @param r the runnable task requested to be executed * @param e the executor attempting to execute this task */ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { } }
- DiscardOldestPolicy:拋棄隊列中等待最久的任務,然後把當前任務加入隊列中。
public static class DiscardOldestPolicy implements RejectedExecutionHandler { /** * Creates a {@code DiscardOldestPolicy} for the given executor. */ public DiscardOldestPolicy() { } /** * Obtains and ignores the next task that the executor * would otherwise execute, if one is immediately available, * and then retries execution of task r, unless the executor * is shut down, in which case task r is instead discarded. * * @param r the runnable task requested to be executed * @param e the executor attempting to execute this task */ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { e.getQueue().poll(); e.execute(r); } } }
- CallerRunsPolicy:調用任務的run()方法繞過線程池直接執行。
public static class CallerRunsPolicy implements RejectedExecutionHandler { /** * Creates a {@code CallerRunsPolicy}. */ public CallerRunsPolicy() { } /** * Executes task r in the caller's thread, unless the executor * has been shut down, in which case the task is discarded. * * @param r the runnable task requested to be executed * @param e the executor attempting to execute this task */ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); } } }
使用方式如下代碼所示:
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
根據之前實現的線程工廠和拒絕策略,一個簡單線程池的相關示例代碼實現如下:
package Test;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class UserThreadPool {
public static void main(String[] args) {
//緩存隊列設置固定長度爲2,爲了快速觸發rejectHandler
BlockingQueue queue = new LinkedBlockingQueue(2);
//假設外部任務線程的來源有機房1和機房2混合調用
UserThreadFactory f1 = new UserThreadFactory("第1機房");
UserThreadFactory f2 = new UserThreadFactory("第2機房");
UserRejectHandler handler = new UserRejectHandler();
//核心線程爲1,最大線程爲2,爲了保證觸發rejectHandler
ThreadPoolExecutor threadPoolFirst = new ThreadPoolExecutor(
1, 2, 60, TimeUnit.SECONDS, queue, f1, handler);
//利用第二個線程工廠實例創建第二個線程池
ThreadPoolExecutor threadPoolSecond = new ThreadPoolExecutor(
1, 2, 60, TimeUnit.SECONDS, queue, f2, handler);
//創建400個任務線程
Runnable task = new Task();
for (int i = 0; i < 200; i++){
threadPoolFirst.execute(task);
threadPoolSecond.execute(task);
}
}
}
運行結果如下:
From UserThreadFactory's 第1機房 -Worker-1
From UserThreadFactory's 第2機房 -Worker-1
From UserThreadFactory's 第1機房 -Worker-2
From UserThreadFactory's 第2機房 -Worker-2
running_2
running_3
running_4
running_5
running_0
running_1
your task is reject. java.util.concurrent.ThreadPoolExecutor@1396fbe[Running, pool size = 2, active thread = 2,
queued tasks =2,completed task =1]
當任務被拒絕時,拒絕策略會打印出當前線程池的大小已經達到了maximumPoolSize=2,且隊列已滿,完成的任務數提示已經有一個(最後一行)。
3、ThreadPoolExecutor部分源碼解析(execute()和addWorker())
在ThreadPoolExecutor的屬性定義中頻繁地用位移運算來表示線程池的狀態,位移運算是改變當前值的一種高效手段,包括左移和右移,下面從屬性定義開始閱讀源碼:
//Integer共有32位,最右邊29位表示工作線程數,最左邊3位表示線程池狀態
//注:簡單地說,3個二進制位可以表示從0到7的八個不同數值(第1處)
private static final int COUNT_BITS = Integer.SIZE - 3;
//000-11111111111111111111111111111,類似於子網掩碼,用於位的與運算,
//得到左邊3位,還是右邊29位
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
//用左邊3位,實現5種線程池狀態。
//111-00000000000000000000000000000,十進制:-536,870,912。
//此狀態表示線程池能夠接受新任務
private static final int RUNNING = -1 << COUNT_BITS;
//000-00000000000000000000000000000,十進制:0。
//此狀態表示不再接受新任務,但可以繼續執行隊列中的任務
private static final int SHUTDOWN = 0 << COUNT_BITS;
//001-00000000000000000000000000000,十進制:536,870,912。
//此狀態表示全面拒絕,並中斷正在處理的任務
private static final int STOP = 1 << COUNT_BITS;
//010-00000000000000000000000000000,十進制:1,073,741,824。
//此狀態表示所有任務已經被終止
private static final int TIDYING = 2 << COUNT_BITS;
//011-00000000000000000000000000000,十進制:1,610,612,736。
//此狀態表示已清理完現場
private static final int TERMINATED = 3 << COUNT_BITS;
//與運算,比如001-00000000000000000000000000011,表示67個工作線程,
//掩碼取反:111-00000000000000000000000000000,即得到左邊3位001,
//表示線程池當前處於STOP狀態
private static int runStateOf(int c) { return c & ~CAPACITY; }
//同理掩碼000-111111111111111111111111111111,得到右邊29位,即工作線程數
private static int workerCountOf(int c) { return c & CAPACITY; }
//把左邊3位與右邊29位按或運算,合併成一個值
private static int ctlOf(int rs, int wc) { return rs | wc; }
第1處說明:線程池的狀態用高3位表示,其中包括了符號位。五種狀態的十進制值按從小到大依次排序爲:
RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED。
這樣設計的好處是可以通過比較值的大小來確定線程池的狀態,例如程序中經常出現isRunning的判斷:
private static boolean isRunning(int c){
return c < SHUTDOWN;
}
Executor接口有且只有一個方法execute,通過參數傳入待執行線程的對象,下面分析ThreadPoolExecutor關於execute方法的實現:
public void execute(Runnable command) {
//JDK中,command的解釋爲:@param command the runnable task
if (command == null)
throw new NullPointerException();
//返回包含線程數及線程池狀態的Integer類型數值
int c = ctl.get();
//如果工作線程數小於核心線程數,則創建線程任務並執行
if (workerCountOf(c) < corePoolSize) {
//addWorker是另一個極爲重要的方法,見下一段源碼解析(第1處)
if (addWorker(command, true))
return;
//如果創建失敗,防止外部已經在線程池中加入新的任務,重新獲取一下
c = ctl.get();
}
//只有當線程池處於RUNNING狀態,才執行後半句:置入隊列
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//如果線程池不是RUNNING狀態,則將剛加入隊列的任務移除
if (! isRunning(recheck) && remove(command))
reject(command);
//如果之前的線程已被消費完,新建一個線程
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//核心池和隊列都已滿,嘗試創建一個新線程
else if (!addWorker(command, false))
//如果addWorker返回是false,即創建失敗,則喚醒拒絕策略(第2處)
reject(command);
}
- 第1處:execute方法在不同的階段有三次addWorker的嘗試動作。
- 第2處:發生拒絕的理由有兩個:(1)線程池狀態爲非RUNNING狀態;(2)等待隊列已滿。
下面分析addWorker方法的源碼:
/**
*根據當前線程池狀態,檢查是否可以添加新的任務線程,如果可以則創建並啓動任務
* 如果一切正常且返回true。返回false的可能性如下:
* 1、線程池沒有處於RUNNING狀態;
* 2、線程工廠創建新的任務線程失敗
*
* firstTask:外部啓動線程池時所需要構造的第一個線程,它是線程的母體
* core:新增工作線程時的判斷指標,解釋如下
* true 表示新增工作線程時,需要判斷當前RUNNING狀態的線程是否少於corePoolSize
* false 表示新增工作線程時,需要判斷當前RUNNING狀態的線程是否少於maximumPoolSize
*/
private boolean addWorker(Runnable firstTask, boolean core) {
//不需要任務預定義的語法標籤,響應下文的continue retry,快速推出多層嵌套循環(第1處)
retry:
for (;;) {
//獲取線程池狀態及線程數
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
//如果是STOP及以上的狀態,或者firstTask初始線程不爲空,或者隊列爲空都會直接返回創建失敗(第2處)
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
//如果超過最大允許線程數則不能再添加新的線程
//最大線程數不能超過2^29,否則影響左邊3位的線程池狀態值
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//將當前活動線程數+1(第3處)
if (compareAndIncrementWorkerCount(c))
break retry;
//線程池狀態和工作線程數是可變化的,需要經常提取這個最新值
c = ctl.get(); // Re-read ctl
//如果已經關閉,則再次從retry標籤處進入,在第2處再做判斷(第4處)
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
//如果線程還是處於RUNNING狀態,那就在說明僅僅是第3處失敗
//繼續循環執行(第5處)
}
}
//開始創建工作線程
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//利用Worker構造方法中的線程池工廠創建線程,並封裝成工作線程Worker對象
w = new Worker(firstTask);
//注意這是Worker中的屬性對象thread(第6處)
final Thread t = w.thread;
if (t != null) {
//在進行ThreadPoolExecutor的敏感操作時都需要持有主鎖
//避免在添加和啓動線程時被幹擾
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
//當線程池狀態爲RUNNING或SHUTDOWN
//且firstTask初始線程爲空時
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
//整個線程池在運行期間的最大併發任務個數
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
//終於看到親切的start()方法
//注意,並非線程池的execute的command參數指向的線程
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
//線程啓動失敗,把剛纔第3處加上的工作線程計數再減回去
addWorkerFailed(w);
}
return workerStarted;
}
- 第1處:配合循環語句出現的label,類似於goto作用。label定義時,必須把標籤和冒號的組合語句緊緊相鄰定義在循環體之前,否則會編譯出錯。目的是實現多重循環時能夠快速推出到任何一層。這種做法的出發點似乎非常貼心,但是在大型軟件項目中,濫用標籤行跳轉的後果將是災難性的。示例代碼中,在retry下方有兩個無限循環,在workerCount加1成功後,直接推出兩層循環。
- 第3處:與第1處的標籤呼應,AtomicInteger對象的加1操作時原子性的。break retry表示直接跳出與retry相鄰的這個循環體。
- 第4處:此continue跳轉至標籤處,繼續執行循環。如果條件爲假,則說明線程池還處於運行狀態,即繼續在for(;;)循環內執行。
- 第5處:compareAndIncrementWorkerCount方法執行失敗的概率非常低。即使失敗,再次執行時成功的概率也是極高的,類似於自旋鎖原理。這裏的處理邏輯是先加1,創建失敗減1,這是輕量級處理併發創建線程的方式。如果先創建線程,成功再加1,當發現超出限制後再銷燬線程,那麼這樣的處理方式明顯比前者代價要大。
- 第6處:Worker對象時工作線程的核心類實現,部分源碼如下:
//它實現Runnable接口,並把本對象作爲參數輸入給run()方法中的runWorker(this), //所以內部屬性線程thread在start的時候,即會調用runWorker方法 private final class Worker extends AbstarctQueueSynchronizer implements Runnable{ Worker(Runnable firstTask){ //它是AbstractQueueSynchronizer的方法 //在runWorker方法執行之前禁止線程被中斷 setState(-1); this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); } //當thread被start()之後,執行runWorkder的方法 public void run(){ runWorker(this); } }
碼出高效中作者的建議:使用線程池要注意以下幾點:
- 合理設置各類參數,應根據實際業務場景來說設置合理地工作線程數。
- 線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。
- 創建線程或線程池時請指定有意義的線程名稱,方便出錯時回溯。
阿里的Java開發手冊中明確提出:線程池不允許使用Executors,而是通過ThreadPoolExecutor的方式創建,這樣的處理方式能更加明確線程池的運行規則,規避資源耗盡的風險(Executors不能指定所有的參數,默認參數可能會造成資源浪費)。