線程池
線程池
對線程統一分配、調優和監控
優點:
- 降低資源消耗
- 提高響應速度
- 提高線程的可管理性
Java1.5中引入的 Executor 框架把任務的提交和執行進行解耦,只需要定義好任務,然後提交給線程池,而不用關心該任務是如何執行、被哪個線程執行,以及什麼時候執行。
public class ExecutorCase{
// 初始化一個包含10個線程的線程池
private static Executor executor = Executors.newFixedThreadPool(10);
public static void main(String [] args){
for(int i=0;i<20;i++){
// 提交20個任務,每個任務打印當前線程名
executor.execute(new Task());
}
}
static class Task implements Runnable{
@Override
public void run(){
System.out.println(Thread.currentThread().getName());
}
}
}
核心參數
corePoolSize
線程池中的核心線程數,當提交一個任務時,線程池創建一個新線程執行任務,直到當前線程數等於corePoolSize;如果當前線程數爲corePoolSize,繼續提交的任務被保存到阻塞隊列中,等待被執行;如果執行了線程池的 prestartAllCoreThreads() 方法,線程池會提前創建並啓動所有核心線程。
maximumPoolSize
線程池中允許的最大線程數。如果當前阻塞隊列滿了,且繼續提交任務,則創建新的線程執行任務,前提是當前線程數小於maximumPoolSize;
keepAliveTime
線程空閒時的存活時間,即當線程沒有任務執行時,繼續存活的時間;默認情況下,該參數只在線程數大於corePoolSize(產生阻塞)時纔有用;
unit
keepAliveTime的單位;
workQueue
用來保存等待被執行的任務的阻塞隊列,且任務必須實現Runable接口,在 JDK 中提供瞭如下阻塞隊列:
- ArrayBlockingQueue:基於數組結構的有界阻塞隊列,按FIFO排序任務;
- LinkedBlockingQuene:基於鏈表結構的阻塞隊列,按FIFO排序任務,吞吐量通常要高於ArrayBlockingQuene;
- SynchronousQuene:一個不存儲元素的阻塞隊列,每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態,吞吐量通常要高於LinkedBlockingQuene;
- priorityBlockingQuene:具有優先級的無界阻塞隊列;
threadFactory
創建線程的工廠,通過自定義的線程工廠可以給每個新建的線程設置一個具有識別度的線程名。
handler
排隊策略和拒絕策略
排隊策略:
1. 直接提交。直接提交策略表示線程池不對任務進行緩存。新進任務直接提交給線程池,當線程池中沒有空閒線程時,創建一個新的線程處理此任務。這種策略需要線程池具有無限增長的可能性。實現爲:SynchronousQueue
2. 有界隊列。當線程池中線程達到corePoolSize時,新進任務被放在隊列裏排隊等待處理。有界隊列(如ArrayBlockingQueue)有助於防止資源耗盡,但是可能較難調整和控制。隊列大小和最大池大小可能需要相互折衷:使用大型隊列和小型池可以最大限度地降低 CPU 使用率、操作系統資源和上下文切換開銷,但是可能導致人工降低吞吐量。如果任務頻繁阻塞(例如,如果它們是 I/O 邊界),則系統可能爲超過您許可的更多線程安排時間。使用小型隊列通常要求較大的池大小,CPU 使用率較高,但是可能遇到不可接受的調度開銷,這樣也會降低吞吐量。
3. 無界隊列。使用無界隊列(例如,不具有預定義容量的 LinkedBlockingQueue)將導致在所有 corePoolSize 線程都忙時新任務在隊列中等待。這樣,創建的線程就不會超過 corePoolSize。(因此,maximumPoolSize 的值也就無效了。)當每個任務完全獨立於其他任務,即任務執行互不影響時,適合於使用無界隊列;例如,在 Web 頁服務器中。這種排隊可用於處理瞬態突發請求,當命令以超過隊列所能處理的平均數連續到達時,此策略允許無界線程具有增長的可能性。
拒絕策略:
線程池的飽和策略,當阻塞隊列滿了,且沒有空閒的工作線程,如果繼續提交任務,必須採取一種策略處理該任務,線程池提供了4種策略:
- AbortPolicy:直接拋出異常,丟棄任務,默認策略;
- CallerRunsPolicy:用調用者所在的線程來執行任務(可能造成當前線程阻塞);
- DiscardOldestPolicy:丟棄阻塞隊列中靠最前的任務,並執行當前任務(不拋異常);
- DiscardPolicy:如果執行程序尚未關閉,則位於工作隊列頭部的任務將被刪除,然後重試執行程序(如果再次失敗,則重複此過程)。
當然也可以根據應用場景實現RejectedExecutionHandler接口,自定義飽和策略,如記錄日誌或持久化存儲不能處理的任務。
Executors
Exectors工廠類提供了線程池的初始化接口,主要有如下幾種:
-
newFixedThreadPool
初始化一個指定線程數的線程池,其中 corePoolSize == maximumPoolSize ,使用 LinkedBlockingQuene 作爲阻塞隊列,不過當線程池沒有可執行任務時,不會釋放線程。
-
newCachedThreadPool
- 初始化一個可以緩存線程的線程池,默認緩存60s,線程池的線程數可達到Integer.MAX_VALUE,即2147483647,內部使用 SynchronousQueue 作爲阻塞隊列;
- 和 newFixedThreadPool 創建的線程池不同,newCachedThreadPool 在沒有任務執行時,當線程的空閒時間超過 keepAliveTime ,會自動釋放線程資源,當提交新任務時,如果沒有空閒線程,則創建新線程執行任務,會導致一定的系統開銷;
所以,使用該線程池時,一定要注意控制併發的任務數,否則創建大量的線程可能導致嚴重的性能問題。
-
newSingleThreadExecutor
初始化的線程池中只有一個線程,如果該線程異常結束,會重新創建一個新的線程繼續執行任務,唯一的線程可以保證所提交任務的順序執行,內部使用LinkedBlockingQueue作爲阻塞隊列。
-
newScheduledThreadPool
初始化的線程池可以在指定的時間內週期性的執行所提交的任務,在實際的業務場景中可以使用該線程池定期的同步數據。
實現原理
除了 newScheduledThreadPool 的內部實現特殊一點之外,其它幾個線程池都是基於**ThreadPoolExecutor **類實現的。
線程池內部狀態
/**
* java.lang.concurrent.ThreadPoolExecutor
*/
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3; //32 - 3
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
//111 29*0 remain
private static final int RUNNING = -1 << COUNT_BITS;
//000 29*0 remain
private static final int SHUTDOWN = 0 << COUNT_BITS;
//001 29*0 remain
private static final int STOP = 1 << COUNT_BITS;
//010 29*0 remain
private static final int TIDYING = 2 << COUNT_BITS;
//011 29*0 remain
private static final int TERMINATED = 3 << COUNT_BITS;
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
其中AtomicInteger變量ctl的功能非常強大:利用低29位表示線程池中線程數,通過高3位表示線程池的運行狀態:
1、RUNNING:-1 << COUNT_BITS,即高3位爲111,該狀態的線程池會接收新任務,並處理阻塞隊列中的任務;
2、SHUTDOWN: 0 << COUNT_BITS,即高3位爲000,該狀態的線程池不會接收新任務,但會處理阻塞隊列中的任務;
3、STOP : 1 << COUNT_BITS,即高3位爲001,該狀態的線程不會接收新任務,也不會處理阻塞隊列中的任務,而且會中斷正在運行的任務;
4、TIDYING : 2 << COUNT_BITS,即高3位爲010;
5、TERMINATED: 3 << COUNT_BITS,即高3位爲011;
任務提交
線程池框架提供了兩種方式提交任務,根據不同的業務需求選擇不同的方式。
-
Executor.execute()
通過Executor.execute()方法提交的任務,必須實現Runnable接口,該方式提交的任務不能獲取返回值,因此無法判斷任務是否執行成功。
-
ExecutorService.submit()
通過ExecutorService.submit()方法提交的任務,可以獲取任務執行完的返回值。
任務執行
當向線程池中提交一個任務,線程池會如何處理該任務?
-
execute實現
/** * Executes the given task sometime in the future. The task * may execute in a new thread or in an existing pooled thread. * * If the task cannot be submitted for execution, either because this * executor has been shutdown or because its capacity has been reached, * the task is handled by the current {@code RejectedExecutionHandler}. * * @param command the task to execute * @throws RejectedExecutionException at discretion of * {@code RejectedExecutionHandler}, if the task * cannot be accepted for execution * @throws NullPointerException if {@code command} is null */ public void execute(Runnable command) { if (command == null) throw new NullPointerException(); /* * Proceed in 3 steps: * * 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. * 1. 如果當前running線程數小於corePoolSize,則以 command 作爲第一個任務啓動一個新的線程 * 在執行addWorker()前,要檢查線程池狀態是否爲RUNNING和當前線程池活動的線程數量, * 通過返回 false 來避免在不該添加線程時添加而產生的警告 * 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. * 2. 如果一個任務成功進入了阻塞隊列,那麼我們仍然需要二次檢查是否應該添加一個線程 * (因爲上次檢查後會有線程died)或者進入本方法之後線程池關閉了。所以重新檢查線程池狀態: * 1.如果線程池停止了是否有回滾入隊操作的必要, * 2.或者當沒有活動線程的時候是否需要啓動一個新的線程。 * * 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. * 3. 如果任務無法進入阻塞隊列(阻塞隊列已滿),我們會試圖創建一個新的線程來執行任務。 * (workerCountOf(c) > corePoolSize && workerCountOf(c) < maximumPoolSize) * 如果創建新線程失敗了,我們就知道線程池已經關閉了,或者線程數已經飽和了 * 所以我們就拒絕這個任務(詳見*拒絕策略) */ int c = ctl.get(); if (workerCountOf(c) < corePoolSize) {//活動線程數小於核心線程數 //創建新的線程執行當前任務,這個線程會用死循環監聽 workQueue if (addWorker(command, true)) //true 代表未達到 corePoolSize return; c = ctl.get(); } //創建線程不成功,或者已經達到 corePoolSize ,把任務丟進阻塞隊列 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); //試圖創建空線程去監聽 workQueue } //無法進入阻塞隊列(已滿),試圖創建新線程(false代表當前線程數超過了 corePoolSize) else if (!addWorker(command, false)) reject(command); //創建不成功就拒絕當前任務 }
具體的執行流程如下:
1、workerCountOf方法根據ctl的低 29 位,得到線程池的當前線程數,如果線程數小於corePoolSize,則執行addWorker方法創建新的線程執行任務;否則執行步驟(2);
2、如果線程池處於RUNNING狀態,且把提交的任務放入阻塞隊列中,放入成功則執行步驟(3),否則執行步驟(4);
3、再次檢查線程池的狀態,如果線程池沒有RUNNING,且成功從阻塞隊列中移除任務,然後執行reject方法拒絕任務。或者線程池RUNNING但第二次檢查發現沒有活動線程,則試圖創建空線程;
4、無法進入阻塞隊列時(已滿),嘗試執行addWorker方法創建新的線程執行任務(此時workerCountOf© > corePoolSize,所以addWorker方法的第二個參數爲false),如果addWoker執行失敗,則執行reject方法拒絕任務;
總結:已創建的線程如果沒有關閉,當前沒有執行任務時,只會從阻塞隊列裏拿任務來執行(newFixedThreadPool的策略是先創建出corePoolSize數目的線程)
-
addWorker實現
從方法execute的實現可以看出:addWorker主要負責創建新的線程並執行任務,代碼實現如下:
/** * Checks if a new worker can be added with respect to current * pool state and the given bound (either core or maximum). If so, * the worker count is adjusted accordingly, and, if possible, a * new worker is created and started, running firstTask as its * first task. This method returns false if the pool is stopped or * eligible to shut down. It also returns false if the thread * factory fails to create a thread when asked. If the thread * creation fails, either due to the thread factory returning * null, or due to an exception (typically OutOfMemoryError in * Thread.start()), we roll back cleanly. * * @param firstTask the task the new thread should run first (or * null if none). Workers are created with an initial first task * (in method execute()) to bypass queuing when there are fewer * than corePoolSize threads (in which case we always start one), * or when the queue is full (in which case we must bypass queue). * Initially idle threads are usually created via * prestartCoreThread or to replace other dying workers. * * @param core if true use corePoolSize as bound, else * maximumPoolSize. (A boolean indicator is used here rather than a * value to ensure reads of fresh values after checking other pool * state). * @return true if successful */ private boolean addWorker(Runnable firstTask, boolean core) { retry: for (;;) { int c = ctl.get(); int rs = runStateOf(c); // Check if queue empty only if necessary. if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && //STOP,TIDYING,TERMINATED 返回 false firstTask == null && //SHUTDOWN 狀態新來的任務不爲null,返回false ! workQueue.isEmpty())) //SHUTDOWN,任務爲null,阻塞隊列空,返回false return false; /* * 上段代碼 拒絕 STOP,TIDYING,TERMINATED狀態的全部新建線程請求, * SHUTDOWN 狀態拒絕新任務 * 放通 RUNNING 狀態全部新建線程請求 * SHUTDOWN 狀態,阻塞隊列有任務(全部線程都在工作)新建空線程請求 */ for (;;) { int wc = workerCountOf(c); //當前活動線程數 if (wc >= CAPACITY || //超過了最大允許線程數 或 達到了規定最大線程數 wc >= (core ? corePoolSize : maximumPoolSize)) return false; 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 } }
這只是addWoker方法實現的前半部分:
1、判斷線程池的狀態,如果線程池的狀態值大於SHUTDOWN(RUNNING狀態的前三位都是1,但是第一位是符號位,代表是個負數,所以它小於SHUTDOWN),則不處理提交的任務,直接返回false;
2、通過參數core判斷當前需要創建的線程是否爲核心線程,如果core爲true,且當前線程數小於corePoolSize,如果core爲false,則則跳出循環,開始創建新的線程,具體實現如下:
boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { w = new Worker(firstTask); //調用內部類 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()); if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { //RUNNING狀態或SHUTDOWN狀態添加 null 任務 if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); //private final HashSet<Worker> workers = new HashSet<Worker>(); workers.add(w); int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; workerAdded = true; } } finally { mainLock.unlock(); // 解鎖 } if (workerAdded) { // 新建 Worker 成功後啓動 Worker 線程 t.start(); workerStarted = true; } } } finally { if (! workerStarted) addWorkerFailed(w); // worker啓動失敗, } return workerStarted; }
線程池的工作線程通過ThreadPoolExecutor類的內部類Woker類實現,在ReentrantLock鎖的保證下,把Woker實例插入到HashSet後,並啓動Woker中的線程,其中Worker類設計如下:
1、繼承了AQS類,可以方便的實現工作線程的中止操作;
2、實現了Runnable接口,可以將自身作爲一個任務在工作線程中執行;
3、當前提交的任務firstTask作爲參數傳入Worker的構造方法;
/** * Class Worker mainly maintains interrupt control state for * threads running tasks, along with other minor bookkeeping. * This class opportunistically extends AbstractQueuedSynchronizer * to simplify acquiring and releasing a lock surrounding each * task execution. This protects against interrupts that are * intended to wake up a worker thread waiting for a task from * instead interrupting a task being run. We implement a simple * non-reentrant mutual exclusion lock rather than use * ReentrantLock because we do not want worker tasks to be able to * reacquire the lock when they invoke pool control methods like * setCorePoolSize. Additionally, to suppress interrupts until * the thread actually starts running tasks, we initialize lock * state to a negative value, and clear it upon start (in * runWorker). */ private final class Worker extends AbstractQueuedSynchronizer implements Runnable { /** * This class will never be serialized, but we provide a * serialVersionUID to suppress a javac warning. */ private static final long serialVersionUID = 6138294804551838833L; /** Thread this worker is running in. Null if factory fails. */ final Thread thread; /** Initial task to run. Possibly null. */ Runnable firstTask; /** Per-thread task counter */ volatile long completedTasks; /** * Creates with given first task and thread from ThreadFactory. * @param firstTask the first task (null if none) */ Worker(Runnable firstTask) { 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(this); } // Lock methods // // The value 0 represents the unlocked state. // The value 1 represents the locked state. protected boolean isHeldExclusively() { return getState() != 0; } protected boolean tryAcquire(int unused) { if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } protected boolean tryRelease(int unused) { setExclusiveOwnerThread(null); setState(0); return true; } public void lock() { acquire(1); } public boolean tryLock() { return tryAcquire(1); } public void unlock() { release(1); } public boolean isLocked() { return isHeldExclusively(); } void interruptIfStarted() { Thread t; if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) { try { t.interrupt(); } catch (SecurityException ignore) { } } } }
從Woker類的構造方法實現可以發現:線程工廠在創建線程thread時,將Woker實例本身this作爲參數傳入,當執行start方法啓動線程thread時,本質是執行了Worker的runWorker方法。
ThreadPoolExecutor.addWorkerFailed() 方法
/** * Rolls back the worker thread creation. * - removes worker from workers, if present * - decrements worker count * - rechecks for termination, in case the existence of this * worker was holding up termination * * 回滾 線程的創建 * - 從 workers 中刪除存在的 worker * - 減少 worker 的數量 * - 重新檢查線程池是否已經 TERMINATED,以防這個 worker 的存在阻止 TERMINATE */ private void addWorkerFailed(Worker w) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { if (w != null) workers.remove(w); decrementWorkerCount(); //確認後 減少一個線程數 tryTerminate(); //停掉線程池 } finally { mainLock.unlock(); } }
ThreadPoolExecutor.tryTerminate() 方法
/** * Transitions to TERMINATED state if either (SHUTDOWN and pool * and queue empty) or (STOP and pool empty). If otherwise * eligible to terminate but workerCount is nonzero, interrupts an * idle worker to ensure that shutdown signals propagate. This * method must be called following any action that might make * termination possible -- reducing worker count or removing tasks * from the queue during shutdown. The method is non-private to * allow access from ScheduledThreadPoolExecutor. * 如果(SHUTDOWN,池和隊列爲空)或(STOP和池爲空),則轉換爲TERMINATED狀態。 * 如果 具備終止條件但 workerCount 非零,則中斷空閒 worker 以確保‘關閉信號’的傳播。 * 必須在可能使線程池終止的任何操作之後調用此方法 * 比如 :在 SHUTDOWN 期間減少 worker 數量或從阻塞隊列中刪除任務。 * 該方法是非私有的,以允許從ScheduledThreadPoolExecutor訪問。 */ final void tryTerminate() { for (;;) { int c = ctl.get(); if (isRunning(c) || runStateAtLeast(c, TIDYING) || (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty())) return; if (workerCountOf(c) != 0) { // Eligible to terminate interruptIdleWorkers(ONLY_ONE); return; } final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) { try { terminated(); } finally { ctl.set(ctlOf(TERMINATED, 0)); termination.signalAll(); } return; } } finally { mainLock.unlock(); } // else retry on failed CAS } }
-
runWorker實現
/** * Main worker run loop. Repeatedly gets tasks from queue and * executes them, while coping with a number of issues: * 主 worker 一直在循環,在處理以下問題時從阻塞隊列中重複獲取任務,執行任務: * 1. We may start out with an initial task, in which case we * don't need to get the first one. Otherwise, as long as pool is * running, we get tasks from getTask. If it returns null then the * worker exits due to changed pool state or configuration * parameters. Other exits result from exception throws in * external code, in which case completedAbruptly holds, which * usually leads processWorkerExit to replace this thread. * 1. 我們執行初始化任務時,不需要獲得第一個任務,否則,只要pool正在運行,我們就會從getTask獲取任務。如果它返回null,則由於更改 池的狀態或配置參數 而退出 worker。 其他退出是由外部代碼中的異常拋出引起的,在這種情況下completedAbruptly持有,這通常導致processWorkerExit替換此線程。 * 2. Before running any task, the lock is acquired to prevent * other pool interrupts while the task is executing, and then we * ensure that unless pool is stopping, this thread does not have * its interrupt set. * 2. 在運行任何任務之前,獲取鎖以防止在執行任務時其他池產生中斷,然後我們確保除非池停止,否則此線程沒有設置其中斷。 * 3. Each task run is preceded by a call to beforeExecute, which * might throw an exception, in which case we cause thread to die * (breaking loop with completedAbruptly true) without processing * the task. * 3. 每個任務運行之前都會調用 beforeExecute,這可能會拋出異常,在這種情況下,我們會導致線程死亡(使 completedAbruptly 爲true跳出循環)而不處理任務。 * 4. Assuming beforeExecute completes normally, we run the task, * gathering any of its thrown exceptions to send to afterExecute. * We separately handle RuntimeException, Error (both of which the * specs guarantee that we trap) and arbitrary Throwables. * Because we cannot rethrow Throwables within Runnable.run, we * wrap them within Errors on the way out (to the thread's * UncaughtExceptionHandler). Any thrown exception also * conservatively causes thread to die. * 4. 假設 beforeExecute 正常完成,我們運行任務,收集任何拋出的異常以發送到 afterExecute。 我們分別處理 RuntimeException,Error(這樣保證我們不陷入陷阱)和任意 Throwables。 因爲我們不能在Runnable.run 方法中重新拋出Throwables,所以我們將它們包含在出錯的Errors中(在線程的UncaughtExceptionHandler 中)。 應該謹慎任何拋出的異常,它們會導致線程死亡。 * 5. After task.run completes, we call afterExecute, which may * also throw an exception, which will also cause thread to * die. According to JLS Sec 14.20, this exception is the one that * will be in effect even if task.run throws. * 5. 在 task.run 方法完成之後,我們調用afterExecute,這也可能引發異常,這也會導致線程死亡。 根據JLS Sec 14.20,即使task.run拋出,該異常也將生效。 * The net effect of the exception mechanics is that afterExecute * and the thread's UncaughtExceptionHandler have as accurate * information as we can provide about any problems encountered by * user code. * * @param w the worker */ final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // allow interrupts boolean completedAbruptly = true; try { //通過 getTask 方法從阻塞隊列中獲取等待的任務 //如果隊列中沒有任務,getTask 方法會被阻塞並掛起,不會佔用cpu資源; while (task != null || (task = getTask()) != null) { w.lock(); // If pool is stopping, ensure thread is interrupted; // if not, ensure thread is not interrupted. This // requires a recheck in second case to deal with // shutdownNow race while clearing interrupt if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { beforeExecute(wt, task); Throwable thrown = null; try { task.run(); } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { afterExecute(task, thrown); } } finally { task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { processWorkerExit(w, completedAbruptly); } }
runWorker方法是線程池的核心:
1、線程啓動之後,通過 unlock 方法釋放鎖,設置 AQS (AbstractQueuedSynchronizer)的 state 爲 0,表示運行中斷;
2、獲取第一個任務 firstTask,執行任務的run方法,不過在執行任務之前,會進行加鎖操作,任務執行完會釋放鎖;
3、在執行任務的前後,可以根據業務場景自定義 beforeExecute 和 afterExecute 方法;
4、firstTask 執行完成之後,通過 getTask 方法從阻塞隊列中獲取等待的任務,如果隊列中沒有任務,getTask 方法會被阻塞並掛起,不會佔用cpu資源;
-
getTask實現
/** * Performs blocking or timed wait for a task, depending on * current configuration settings, or returns null if this worker * must exit because of any of: * 1. There are more than maximumPoolSize workers (due to * a call to setMaximumPoolSize). * 2. The pool is stopped. * 3. The pool is shutdown and the queue is empty. * 4. This worker timed out waiting for a task, and timed-out * workers are subject to termination (that is, * {@code allowCoreThreadTimeOut || workerCount > corePoolSize}) * both before and after the timed wait, and if the queue is * non-empty, this worker is not the last thread in the pool. * * @return task, or null if the worker must exit, in which case * workerCount is decremented */ private Runnable getTask() { boolean timedOut = false; // Did the last poll() time out? for (;;) { int c = ctl.get(); int rs = runStateOf(c); // Check if queue empty only if necessary. // STOP 狀態不處理任務,workQueue 爲空也直接返回 if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount();//將 workerCount - 1 return null; } int wc = workerCountOf(c); // Are workers subject to culling? boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { if (compareAndDecrementWorkerCount(c)) return null; continue; } try { // 有超時時間就按超時時間調用,沒有就直接出隊一個任務 Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; timedOut = true; // workQueue.poll 因超時而返回 null } catch (InterruptedException retry) { timedOut = false; } } }
整個 getTask 操作在 自旋 下完成:
1、workQueue.take:如果阻塞隊列爲空,當前線程會被掛起等待;當隊列中有任務加入時,線程被喚醒,take方法返回任務,並執行;
2、workQueue.poll:如果在keepAliveTime時間內,阻塞隊列還是沒有任務,則返回null;
所以,線程池中實現的線程可以一直執行由用戶提交的任務。
-
Future和Callable實現(應用)
public class ExecutorCase { private static ExecutorService executor = Executors.newFixedThreadPool(10); public static void main(String[] args) { // Future 負責獲取結果 Future<String> future = executor.submit(new Task()); System.out.println("do other things"); try { // 阻塞 main 線程,等待線程池返回 String result = future.get(); System.out.println(result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } static class Task implements Callable<String>{ // Callable 負責產生結果 @Override public String call() throws Exception { try { TimeUnit.SECONDS.sleep(2); }catch (InterruptedException e) { e.printStackTrace(); } return "this is future case"; } } }
運行結果:
->do other things ->this is future case
在實際業務場景中,Future 和 Callable 基本是成對出現的,Callable 負責產生結果,Future 負責獲取結果。
1、Callable 接口類似於 Runnable,只是 Runnable 沒有返回值。
2、Callable 任務除了返回正常結果之外,如果發生異常,該異常也會被返回,即 Future 可以拿到異步執行任務各種結果;
3、Future.get 方法會導致主線程阻塞,直到Callable任務執行完成;
-
submit實現
/** * @throws RejectedExecutionException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ public Future<?> submit(Runnable task) { if (task == null) throw new NullPointerException(); // return new FutureTask<T>(callable); RunnableFuture<Void> ftask = newTaskFor(task, null); execute(ftask); return ftask; }
通過 submit 方法提交的 Callable 任務會被封裝成了一個 FutureTask 對象,提交後返回了任務對象。
FutureTask
/** * The run state of this task, initially NEW. The run state * transitions to a terminal state only in methods set, * setException, and cancel. During completion, state may take on * transient values of COMPLETING (while outcome is being set) or * INTERRUPTING (only while interrupting the runner to satisfy a * cancel(true)). Transitions from these intermediate to final * states use cheaper ordered/lazy writes because values are unique * and cannot be further modified. * * Possible state transitions: * NEW -> COMPLETING -> NORMAL * NEW -> COMPLETING -> EXCEPTIONAL * NEW -> CANCELLED * NEW -> INTERRUPTING -> INTERRUPTED */ private volatile int state; private static final int NEW = 0; private static final int COMPLETING = 1; private static final int NORMAL = 2; private static final int EXCEPTIONAL = 3; private static final int CANCELLED = 4; private static final int INTERRUPTING = 5; private static final int INTERRUPTED = 6;
1、FutureTask 在不同階段擁有不同的狀態 state,初始化爲NEW;
2、FutureTask 類實現了 Runnable 接口,這樣就可以通過 Executor.execute 方法提交 FutureTask 到線程池中等待被執行,最終執行的是 FutureTask 的 run 方法;
-
FutureTask.get實現
/** * @throws CancellationException {@inheritDoc} */ public V get() throws InterruptedException, ExecutionException { int s = state; if (s <= COMPLETING) s = awaitDone(false, 0L); return report(s); }
內部通過 awaitDone 方法對主線程進行阻塞,具體實現如下:
/** * Awaits completion or aborts on interrupt or timeout. * * @param timed true if use timed waits * @param nanos time to wait, if timed * @return state upon completion */ private int awaitDone(boolean timed, long nanos) throws InterruptedException { final long deadline = timed ? System.nanoTime() + nanos : 0L; WaitNode q = null; boolean queued = false; for (;;) { if (Thread.interrupted()) { removeWaiter(q); throw new InterruptedException(); } int s = state; if (s > COMPLETING) { if (q != null) q.thread = null; return s; } else if (s == COMPLETING) // cannot time out yet Thread.yield(); else if (q == null) q = new WaitNode(); else if (!queued) queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q); else if (timed) { nanos = deadline - System.nanoTime(); if (nanos <= 0L) { removeWaiter(q); return state; } LockSupport.parkNanos(this, nanos); } else LockSupport.park(this); } }
1、如果主線程被中斷,則拋出中斷異常;
2、判斷 FutureTask 當前的 state ,如果大於 COMPLETING ,說明任務已經執行完成,則直接返回;
3、如果當前 state 等於 COMPLETING ,說明任務已經執行完,這時主線程只需通過 yield 方法讓出 cpu 資源,等待 state 變成 NORMAL ;
4、通過 WaitNode 類封裝當前線程,並通過 UNSAFE 添加到 waiters 鏈表;
5、最終通過 LockSupport 的 park 或 parkNanos 掛起線程;
-
FutureTask.run實現
public void run() { if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())) return; try { Callable<V> c = callable; if (c != null && state == NEW) { V result; boolean ran; try { result = c.call(); ran = true; } catch (Throwable ex) { result = null; ran = false; setException(ex); } if (ran) set(result); } } finally { // runner must be non-null until state is settled to // prevent concurrent calls to run() runner = null; // state must be re-read after nulling runner to prevent // leaked interrupts int s = state; if (s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); } }
FutureTask.run 方法是在線程池中被執行的,而非主線程
1、通過執行 Callable 任務的 call 方法;
2、如果 call 執行成功,則通過 set 方法保存結果;
3、如果 call 執行有異常,則通過 setException 保存異常;
-
set
/** * Sets the result of this future to the given value unless * this future has already been set or has been cancelled. * * <p>This method is invoked internally by the {@link #run} method * upon successful completion of the computation. * * @param v the value */ protected void set(V v) { if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) { outcome = v; UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state finishCompletion(); } }
-
setException
/** * Causes this future to report an {@link ExecutionException} * with the given throwable as its cause, unless this future has * already been set or has been cancelled. * * <p>This method is invoked internally by the {@link #run} method * upon failure of the computation. * * @param t the cause of failure */ protected void setException(Throwable t) { if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) { outcome = t; UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state finishCompletion(); } }
set 和 setException 方法中,都會通過 UNSAFE修改 FutureTask 的狀態,並執行 finishCompletion 方法通知主線程任務已經執行完成;
-
finishCompletion
/** * Removes and signals all waiting threads, invokes done(), and * nulls out callable. */ private void finishCompletion() { // assert state > COMPLETING; for (WaitNode q; (q = waiters) != null;) { if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) { for (;;) { Thread t = q.thread; if (t != null) { q.thread = null; LockSupport.unpark(t); } WaitNode next = q.next; if (next == null) break; q.next = null; // unlink to help gc q = next; } break; } } done(); callable = null; // to reduce footprint }
1、執行 FutureTask 類的 get 方法時,會把主線程封裝成 WaitNode 節點並保存在 waiters 鏈表中;
2、FutureTask 任務執行完成後,通過 UNSAFE 設置 waiters 的值,並通過 LockSupport 類 unpark 方法喚醒主線程;