Java中使用線程池技術一般都是使用Executors
這個工廠類,它提供了非常簡單方法來創建各種類型的線程池:
public static ExecutorService newFixedThreadPool(int nThreads) public static ExecutorService newSingleThreadExecutor() public static ExecutorService newCachedThreadPool() public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
核心的接口其實是Executor
,它只有一個execute
方法抽象爲對任務(Runnable接口)的執行, ExecutorService
接口在Executor
的基礎上提供了對任務執行的生命週期的管理,主要是submit
和shutdown
方法, AbstractExecutorService
對ExecutorService
一些方法做了默認的實現,主要是submit和invoke方法,而真正的任務執行
的Executor接口execute
方法是由子類實現,就是ThreadPoolExecutor
,它實現了基於線程池的任務執行框架,所以要了解
JDK的線程池,那麼就得先看這個類。
再看execute
方法之前需要先介幾個變量或類。
ctl
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
這個變量是整個類的核心,AtomicInteger
保證了對這個變量的操作是原子的,通過巧妙的操作,ThreadPoolExecutor用這一個變量保存了兩個內容:
- 所有有效線程的數量
- 各個線程的狀態(runState)
低29位存線程數,高3位存runState
,這樣runState有5個值:
- RUNNING:-536870912
- SHUTDOWN:0
- STOP:536870912
- TIDYING:1073741824
- TERMINATED:1610612736
線程池中各個狀態間的轉換比較複雜,主要記住下面內容就可以了:
- RUNNING狀態:線程池正常運行,可以接受新的任務並處理隊列中的任務;
- SHUTDOWN狀態:不再接受新的任務,但是會執行隊列中的任務;
- STOP狀態:不再接受新任務,不處理隊列中的任務
圍繞ctl變量有一些操作,瞭解這些方法是看懂後面一些晦澀代碼的基礎:
corePoolSize
核心線程池大小,活動線程小於corePoolSize則直接創建,大於等於則先加到workQueue中,隊列滿了才創建新的線程。
keepAliveTime
線程從隊列中獲取任務的超時時間,也就是說如果線程空閒超過這個時間就會終止。
Worker
private final class Worker extends AbstractQueuedSynchronizer implements Runnable ...
內部類Worker
是對任務的封裝,所有submit的Runnable都被封裝成了Worker,它本身也是一個Runnable,
然後利用AQS框架(關於AQS可以看我這篇文章)實現了一個簡單的非重入的互斥鎖, 實現互斥鎖主要目的是爲了中斷的時候判斷線程是在空閒還是運行,可以看後面shutdown
和shutdownNow
方法的分析。
// state只有0和1,互斥 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; }
之所以不用ReentrantLock是爲了避免任務執行的代碼中修改線程池的變量,如setCorePoolSize
,因爲ReentrantLock是可重入的。
execute
execute
方法主要三個步驟:
- 活動線程小於corePoolSize的時候創建新的線程;
- 活動線程大於corePoolSize時都是先加入到任務隊列當中;
- 任務隊列滿了再去啓動新的線程,如果線程數達到最大值就拒絕任務。
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); // 活動線程數 < corePoolSize if (workerCountOf(c) < corePoolSize) { // 直接啓動新的線程。第二個參數true:addWorker中會重新檢查workerCount是否小於corePoolSize if (addWorker(command, true)) // 添加成功返回 return; c = ctl.get(); } // 活動線程數 >= corePoolSize // runState爲RUNNING && 隊列未滿 if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); // double check // 非RUNNING狀態 則從workQueue中移除任務並拒絕 if (!isRunning(recheck) && remove(command)) reject(command);// 採用線程池指定的策略拒絕任務 // 線程池處於RUNNING狀態 || 線程池處於非RUNNING狀態但是任務移除失敗 else if (workerCountOf(recheck) == 0) // 這行代碼是爲了SHUTDOWN狀態下沒有活動線程了,但是隊列裏還有任務沒執行這種特殊情況。 // 添加一個null任務是因爲SHUTDOWN狀態下,線程池不再接受新任務 addWorker(null, false); // 兩種情況: // 1.非RUNNING狀態拒絕新的任務 // 2.隊列滿了啓動新的線程失敗(workCount > maximumPoolSize) } else if (!addWorker(command, false)) reject(command); }
註釋比較清楚了就不再解釋了,其中比較難理解的應該是addWorker(null,
false);
這一行,這要結合addWorker一起來看。 主要目的是防止HUTDOWN狀態下沒有活動線程了,但是隊列裏還有任務沒執行這種特殊情況。
addWorker
這個方法理解起來比較費勁。
runWorker
任務添加成功後實際執行的是runWorker
這個方法,這個方法非常重要,簡單來說它做的就是:
- 第一次啓動會執行初始化傳進來的任務firstTask;
- 然後會從workQueue中取任務執行,如果隊列爲空則等待keepAliveTime這麼長時間。
getTask
processWorkerExit
線程退出會執行這個方法做一些清理工作。
tryTerminate
processWorkerExit方法中會嘗試調用tryTerminate來終止線程池。這個方法在任何可能導致線程池終止的動作後執行:比如減少wokerCount或SHUTDOWN狀態下從隊列中移除任務。
shutdown和shutdownNow
shutdown這個方法會將runState置爲SHUTDOWN
,會終止所有空閒的線程。
public void shutdown() { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { checkShutdownAccess(); // 線程池狀態設爲SHUTDOWN,如果已經至少是這個狀態那麼則直接返回 advanceRunState(SHUTDOWN); // 注意這裏是中斷所有空閒的線程:runWorker中等待的線程被中斷 → 進入processWorkerExit → // tryTerminate方法中會保證隊列中剩餘的任務得到執行。 interruptIdleWorkers(); onShutdown(); // hook for ScheduledThreadPoolExecutor } finally { mainLock.unlock(); } tryTerminate(); }
shutdownNow方法將runState置爲STOP。和shutdown方法的區別,這個方法會終止所有的線程。
public List<Runnable> shutdownNow() { List<Runnable> tasks; final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { checkShutdownAccess(); // STOP狀態:不再接受新任務且不再執行隊列中的任務。 advanceRunState(STOP); // 中斷所有線程 interruptWorkers(); // 返回隊列中還沒有被執行的任務。 tasks = drainQueue(); } finally { mainLock.unlock(); } tryTerminate(); return tasks; }
主要區別在於shutdown調用的是interruptIdleWorkers
這個方法,而shutdownNow實際調用的是Worker類的interruptIfStarted
方法:
private void interruptIdleWorkers(boolean onlyOne) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { for (Worker w : workers) { Thread t = w.thread; // w.tryLock能獲取到鎖,說明該線程沒有在運行,因爲runWorker中執行任務會先lock, // 因此保證了中斷的肯定是空閒的線程。 if (!t.isInterrupted() && w.tryLock()) { try { t.interrupt(); } catch (SecurityException ignore) { } finally { w.unlock(); } } if (onlyOne) break; } } finally { mainLock.unlock(); } }
void interruptIfStarted() { Thread t; // 初始化時state == -1 if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) { try { t.interrupt(); } catch (SecurityException ignore) { } } }
這就是前面提到的Woker類實現AQS的主要作用。
注意:shutdown方法可能會在finalize被隱式的調用。
這篇博客基本都是代碼跟註釋,所以如果不是分析ThreadPoolExecutor
源碼的話看起來會非常無聊。