在多線程編程中,或多或少都聽過或者使用過線程池,合理利用線程池能夠帶來三個好處。 降低資源消耗。通過重複利用已建立的線程降低線程建立和銷毀造成的消耗。 提高響應速度。當任務到達時,任務可以不需要等到線程建立就能立即執行。 提高線程的可管理性。線程是稀缺資源,如果無限制的建立,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。
但是要做到合理的利用線程池,必須對其原理比較瞭解,今天我們就來簡要的分析線程池的實現原理—源碼面前無秘密 線程池的基礎架構(jdk 1.8)
為了更好的理解線程池,還是先來梳理一下它的架構,有助於我們的分析,不然很混亂。 繼承體系
Executor
Executor,任務的執行器,線程池架構中幾乎所有類都直接或者間接實現Executor介面,它是線程池架構的基礎。它將任務的提交與任務的執行分離開來(生產者–消費者模式),它僅提供了一個Execute()方法用來執行已經提交的Runnable任務。
/** * Executes the given command at some time in the future. The command * may execute in a new thread, in a pooled thread, or in the calling * thread, at the discretion of the {@code Executor} implementation. * * @param command the runnable task * @throws RejectedExecutionException if this task cannot be * accepted for execution * @throws NullPointerException if command is null */void execute(Runnable command);
ExcutorService
ExcutorService也是介面,繼承Executor介面,通過名稱我們知道它提供的是一種服務,ExecutorService提供了 將任務提交給執行者的介面(submit方法)以及 讓執行者執行任務(invokeAll, invokeAny方法) 的介面等等。
public interface ExecutorService extends Executor { /** * 關閉線程池,已經提交的任務會得到執行,但不接受新任務 * 該方法不會等待已經提交的任務執行完成 */ void shutdown(); /** * 試圖停止所有正在執行的活動任務,暫停處理正在等待的任務 * 返回等待執行的工作清單 * 該方法不會等待正在執行的任務終止。 */ List<Runnable> shutdownNow(); /** * 如果執行器已關閉,則返回 true。 */ boolean isShutdown(); /** * 如果關閉後所有任務都已完成,則返回 true * 注意:只有shutdown或者shutdownNow先被調用,isTerminated才可能返回true */ boolean isTerminated(); /** * shutdown request、or the timeout occurs, or the current thread is * interrupted,都將導致阻塞,直到所有任務執行完成 */ boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException; /** * 提交任務用於執行,返回Future,可以通過Future 獲得任務執行結果 */ <T> Future<T> submit(Callable<T> task); /** * 提交一個 Runnable 任務用於執行,並返回一個表示該任務的 Future */ <T> Future<T> submit(Runnable task, T result); /** * 提交一個 Runnable 任務用於執行,並返回一個表示該任務的 Future */ Future<?> submit(Runnable task); /** * 執行給定的任務,當所有任務完成時,返回保持任務狀態和結果的 Future 列表 */ <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException; /** * 執行給定的任務,當所有任務完成或逾時期滿時(無論哪個首先發生),返回保持任務狀態和結果的 Future 列表 */ <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException; /** * 執行給定的任務,如果某個任務已成功完成(也就是未拋出異常),則返回其結果 */ <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException; /** * 執行給定的任務,如果在給定的逾時期滿前某個任務已成功完成(也就是未拋出異常),則返回其結果 */ <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;}
源碼上有很全的注釋,可以很好的協助理解。 AbstractExecutorService
AbstractExecutorService 抽象類別,實現ExecutorService介面,提供了預設實現,下面看看幾個我們常使用到的方法。
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) { return new FutureTask<T>(callable);}protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { return new FutureTask<T>(runnable, value);}
將Callable 對象封裝成FutureTask 對象,在前一篇文章中,我們分析了FutureTask,因此這裡就不在分析了,可以參考:Java 多線程 — FutureTask 源碼分析
// 提交Runnable 任務public Future<?> submit(Runnable task) { if (task == null) throw new NullPointerException(); RunnableFuture<Void> ftask = newTaskFor(task, null); //執行,execute方法交給具體的子類來實現 execute(ftask); return ftask;}// 提交Runnable 任務,並指定結果public <T> Future<T> submit(Runnable task, T result) { if (task == null) throw new NullPointerException(); RunnableFuture<T> ftask = newTaskFor(task, result); execute(ftask); return ftask;}// 提交Callable任務public <T> Future<T> submit(Callable<T> task) { if (task == null) throw new NullPointerException(); RunnableFuture<T> ftask = newTaskFor(task); execute(ftask); return ftask;}
大致過程就是根據提交的任務封裝成FutureTask 對象,然後再執行任務,execute 方法交由具體的子類來實現。 ThreadPoolExecutor
這個我們稍後將會詳細介紹 ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor繼承ThreadPoolExecutor來重用線程池的功能,ScheduledThreadPoolExecutor用來支援週期性任務的調度,這個後面我們再分析。 ThreadPoolExecutor
Java的線程池支援主要通過ThreadPoolExecutor來實現,我們使用的ExecutorService的各種線程池策略都是基於ThreadPoolExecutor來實現的,所以ThreadPoolExecutor十分重要,要弄明白各種線程池策略,必須先弄明白ThreadPoolExecutor。 資料結構
/** * 整個線程池的控制狀態,包含了兩個屬性:有效線程的數量、線程池的狀態(runState)。 * workerCount,有效線程的數量(低29位) * runState, 線程池的狀態(高3位) * ctl 包含32位元據,低29位存線程數,高3位存runState,runState有5個值: * RUNNING: 接受新任務,處理任務隊列中的任務 * SHUTDOWN: 不接受新任務,處理任務隊列中的任務 * STOP: 不接受新任務,不處理任務隊列中的任務 * TIDYING: 所有任務完成,線程數為0,然後執行terminated() * TERMINATED: terminated() 已經完成 * 狀態轉換: * RUNNING -> SHUTDOWN :調用shutdown()方法 * (RUNNING or SHUTDOWN) -> STOP :調用了shutdownNow()方法 * SHUTDOWN -> TIDYING :隊列裡面沒有任務了,同時線程池沒有線程了 * STOP -> TIDYING:線程池沒有線程了 * TIDYING -> TERMINATED :terminated()方法調用完成 */private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); // 29位的位移量 private static final int COUNT_BITS = Integer.SIZE - 3; // 最大容量(2^29 - 1) private static final int CAPACITY = (1 << COUNT_BITS) - 1; // 阻塞隊列(任務隊列) private final BlockingQueue<Runnable> workQueue; // 可重新進入鎖 private final ReentrantLock mainLock = new ReentrantLock(); // 背景工作執行緒集合 private final HashSet<Worker> workers = new HashSet<Worker>(); // 終止條件 private final Condition termination = mainLock.newCondition(); // 線程池最大容量 private int largestPoolSize; // 已完成的任務數量 private long completedTaskCount; // 線程工廠(產生線程) private volatile ThreadFactory threadFactory; // 線程池拒絕任務策略 private volatile RejectedExecutionHandler handler; // 線程池空閑時,線程存活的時間 private volatile long keepAliveTime; // 是否運行核心線程逾時 private volatile boolean allowCoreThreadTimeOut; // 核心線程池大小 private volatile int corePoolSize; // 最大線程池大小 private volatile int maximumPoolSize; //擷取線程池狀態 private static int runStateOf(int c) { return c & ~CAPACITY; } //擷取背景工作執行緒數 private static int workerCountOf(int c) { return c & CAPACITY; } //擷取ctl private static int ctlOf(int rs, int wc) { return rs | wc; }
ctl 屬性
狀態變數ctl:包含了兩個屬性:有效線程的數量(workerCount:低29位)、線程池的狀態(runState:高3位)。
runState有5個值: RUNNING: 接受新任務,處理任務隊列中的任務 SHUTDOWN: 不接受新任務,處理任務隊列中的任務 STOP: 不接受新任務,不處理任務隊列中的任務 TIDYING: 所有任務完成,線程數為0,然後執行terminated() TERMINATED: terminated() 已經完成
狀態轉換:
RUNNING -> SHUTDOWN :調用shutdown()方法
(RUNNING or SHUTDOWN) -> STOP :調用了shutdownNow()方法
SHUTDOWN -> TIDYING :隊列裡面沒有任務了,同時線程池沒有線程了
STOP -> TIDYING:線程池沒有線程了
TIDYING -> TERMINATED :terminated()方法調用完成 corePoolSize
線程池中核心線程的數量。當提交一個任務時,線程池會建立一個線程來執行任務,直到當前線程數等於corePoolSize。如果調用了線程池的prestartAllCoreThreads()方法,線程池會提前建立並啟動所有基本線程。 maximumPoolSize
線程池中允許的最大線程數。線程池的阻塞隊列滿了之後,如果還有任務提交,如果當前的線程數小於maximumPoolSize,則會建立線程來執行任務。注意,如果使用的是無界隊列,該參數也就沒有什麼效果了。 keepAliveTime
線程閒置時間。線程的建立和銷毀是需要代價的。線程執行完任務後不會立即銷毀,而是繼續存活一段時間:keepAliveTime。預設情況下,該參數只有線上程數大於corePoolSize時才會生效。 workQueue
用來儲存等待執行的任務的阻塞隊列,等待的任務必須實現Runnable介面。 RejectedExecutionHandler
RejectedExecutionHandler,線程池的拒絕策略。所謂拒絕策略,是指將任務添加到線程池中時,線程池拒絕該任務所採取的相應策略。當向線程池中提交任務時,如果此時線程池中的線程已經飽和了,而且阻塞隊列也已經滿了,則線程池會選擇一種拒絕策略來處理該任務。
線程池提供了四種拒絕策略:
1. AbortPolicy:直接拋出異常,預設策略;
2. CallerRunsPolicy:用調用者所在的線程來執行任務;
3. DiscardOldestPolicy:丟棄阻塞隊列中靠最前的任務,並執行當前任務;
4. DiscardPolicy:直接丟棄任務;
當然我們也可以實現自己的拒絕策略,實現RejectedExecutionHandler介面即可。 線程池處理流程
線程池處理流程如下(來自《Java並發編程的藝術》):
ThreadPoolExecutor 執行execute 方法分下面4種
1).如果當前啟動並執行線程小於corePoolSize,則建立新線程執行任務(任務無需入阻塞隊列)。
2).如果當啟動並執行線程大於等於CorePoolSize,那麼將任務加入到BlockingQueue。
3).如果不能加入BlockingQueue(隊列已滿),如果線程數小於MaxPoolSize,則建立線程執行任務。
4).如果線程數大於等於MaxPoolSize,那麼執行拒絕策略。
構造方法
線程池的建立可以通過ThreadPoolExecutor的構造方法實現:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || 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;}
參數的各個含義在上面已經分析過了。 向線程池提交任務
可以使用兩個方法向線程池提交任務,execute()和submit()方法。
execute()方法用於提交不需要傳回值的任務,所以無法判斷任務是否被線程池執行成功。
submit()方法用於提交需要傳回值的任務。線程池會返回一個future類型的對象,通過這個future對象可以判斷任務是否執行成功,並且可以通過future的get()來擷取傳回值,get()方法會阻塞當前線程知道任務完成,而使用get(long timeout,TimeUnit unit)方法則會阻塞當前線程一段時間後立即返回,這時候有可能任務還沒有執行完。 submit
submit 方法我們在AbstractExecutorService 中已經分析了,可以參考前面AbstractExecutorService的分析。 execute
/** 1. Executes the given task sometime in the future. The task 2. may execute in a new thread or in an existing pooled thread. 3. If the task cannot be submitted for execution, either because this 4. executor has been shutdown or because its capacity has been reached, 5. the task is handled by the current {@code RejectedExecutionHandler}. */public void execute(Runnable command) { if (command == null) throw new NullPointerException(); /* * Proceed in 3 steps: * 1. 如果啟動並執行線程小於corePoolSize,則嘗試建立一個新的線程來執行任務, * 調用addWorker函數會原子性的檢查runState和workCount,保證正確的添加線程 * 1. If fewer than corePoolSize threads are running, try to * start a new thread with the given command as its first * task. The call to addWorker atomically checks runState and * workerCount, and so prevents false alarms that would add * threads when it shouldn't, by returning false. * 2. 如果一個任務能夠成功入隊列,在添加一個線程時仍需要進行雙重檢查(因為在前一次檢查後該線程死亡了), * 或者當進入到此方法時,線程池已經shutdown了,所以需要再次檢查狀態,若有必要,當停止時還需要復原入隊列操作, * 或者當線程池沒有線程時需要建立一個新線程 * 2. If a task can be successfully queued, then we still need * to double-check whether we should have added a thread * (because existing ones died since last checking) or that * the pool shut down since entry into this method. So we * recheck state and if necessary roll back the enqueuing if * stopped, or start a new thread if there are none. * *3. 如果無法入隊列,那麼會嘗試增加一個新線程,如果此操作失敗, * 那麼就意味著線程池已經shutdown或者已經飽和了,所以拒絕任務 * 3. If we cannot queue task, then we try to add a new * thread. If it fails, we know we are shut down or saturated * and so reject the task. */ //擷取狀態變數 int c = ctl.get(); //通過workerCountOf方法擷取當前有效線程數,如果小於corePoolSize,則嘗試建立線程來執行任務 if (workerCountOf(c) < corePoolSize) { //建立背景工作執行緒來執行任務 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) // 線程池處於SHUTDOWN狀態,沒有活動線程了,但是隊列裡還有任務沒執行這種特殊情況,可能需要添加背景工作執行緒來執行任務 addWorker(null, false); } else if (!addWorker(command, false)) //則嘗試建立新線程來執行任務 reject(command); // 失敗,執行拒絕策略(有效線程數>=maximumPoolSize)}
執行流程大致如下:
如果線程池有效線程數小於corePoolSize,則調用addWorker建立新線程執行任務,成功返回true,失敗則執行步驟2。
如果線程池處於RUNNING狀態,則嘗試將任務排入佇列隊列,如果排入佇列成功,則嘗試進行Double Check,如果加入失敗,則執行步驟3。 如果線程池不是RUNNING狀態或者排入佇列失敗,則嘗試建立新線程直到有效線程達到maxPoolSize,如果失敗,則調用reject()方法執行拒絕策略。
Double Check 主要目的是判斷加入到隊列隊列中的線程是否可以被執行。如果線程池不是RUNNING狀態,則調用remove()方法從阻塞隊列中刪除該任務,然後調用reject()方法處理任務。
Worker
在前面,我們多次提到了背景工作執行緒,現在我們來揭開它的面紗,Worker是真正的任務,是由任務執行線程完成。每個Worker對象中,包含一個需要立即執行的新任務和已經執行完成的任務數量,Worker本身,是一個Runnable對象,繼承了AbstractQueuedSynchronizer,因此Worker本身也是一個同步群組件。
屬性
//worker持有的線程final Thread thread;/** Initial task to run. Possibly null. *///具體的任務Runnable firstTask;/** Per-thread task counter */volatile long completedTasks;Worker(Runnable firstTask) { // 初始化同步狀態,在這種狀態下,無法中斷,見interruptIfStarted() 方法 setState(-1); // inhibit interrupts until runWorker this.firstTask = firstTask; //建立線程 this.thread = getThreadFactory().newThread(this);}/** Delegates main run loop to outer runWorker */public void run() { //委託給runWorker runWorker(this);}void interruptIfStarted() { Thread t; if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) { try { t.interrupt(); } catch (SecurityException ignore) { } }}...// 省略部分方法
它內部封裝一個Thread對象,用此對象執行本身的run方法,而這個Thread對象則由ThreadPoolExecutor提供的ThreadFactory對象建立新的線程。(將Worker和Thread分離的好處是,如果我們的業務代碼,需要對於線程池中的線程,賦予優先順序、線程名稱、線程執行策略等其他控制時,可以實現自己的ThreadFactory進行擴充,無需繼承或改寫ThreadPoolExecutor) addWorker
private boolean addWorker(Runnable firstTask, boolean core) { retry: for (;;) { int c = ctl.get(); //線程池狀態 int rs = runStateOf(c); /** * 如果線程池為SHUTDOWN,則需要判斷任務隊列中是否還有任務,如果有任務,需要執行 */ if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) return false; for (;;) { //有效背景工作執行緒數 int wc = workerCountOf(c); //worker數量大於等於最大容量 if (wc >= CAPACITY || // worker數量大於等於核心線程池大小或者最大線程池大小 wc >= (core ? corePoolSize : maximumPoolSize)) return false; //遞增worker的數量 if (compareAndIncrementWorkerCount(c)) break retry; //狀態變數 c = ctl.get(); // Re-read ctl // 此次的狀態與上次擷取的狀態不相同 if (runStateOf(c) != rs) continue retry; // else CAS failed due to workerCount change; retry inner loop } } boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { //把任務封裝在Worker中 w = new Worker(firstTask); final Thread t = w.thread; if (t != null) { final ReentrantLock mainLock = this.mainLock; //加鎖 mainLock.lock(); try { // Recheck while holding lock. // Back out on ThreadFactory failure or if // shut down before lock acquired. //線程池狀態 int rs = runStateOf(ctl.get()); // 如果線程池處於RUNNING狀態執行新增工作操作,或線程池處於SHUTDOWN 狀態,firstTask 為空白(任務隊列不為空白,需要添加背景工作執行緒來執行任務) if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); // 將worker添加到worker集合 workers.add(w); int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; workerAdded = true; } } finally { //釋放鎖 mainLock.unlock(); } if (workerAdded) { //啟動線程,執行任務 t.start(); workerStarted = true; } } } finally { //背景工作執行緒建立失敗 if (! workerStarted) addWorkerFailed(w); } return workerStarted; }
addWorker大致流程如下: 判斷線程池狀態,如果需要建立背景工作執行緒,則原子性的增加workerCount。 將任務封裝成為一個worker,並將此worker添加進workers集合中。 啟動worker對應的線程,並啟動該線程,運行worker的run方法。 如果失敗了復原worker的建立動作(addWorkerFailed 方法),即將worker從workers集合中刪除,並原子性的減少workerCount。 runWorker
final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; //釋放鎖(設定state為0,允許中斷) w.unlock(); // allow interrupts boolean completedAbruptly = true; try { //任務不為null或者阻塞隊列還存在任務 while (task != null || (task = getTask()) !=