一、問什麼要使用執行緒池?
1.降低系統資源消耗
2.提高執行緒可控性。
二、如何建立使用的執行緒池?
jdk8提供了五種建立執行緒池的方法:
1.建立一個定長執行緒池,可控制執行緒最大併發數,超出的執行緒會在佇列中等待。
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(
nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()); } 複製程式碼
2.jdk8新增的,會根據所需的併發數來動態建立和關閉執行緒。能合理的使用CPU進行對任務進行併發操作,所以適合使用在很耗時的任務。
注意返回的是ForkJoinPool
public static ExecutorService newWorkStealingPool(int parallelism) {
return new ForkJoinPool (
parallelism, ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true);
} 複製程式碼
什麼是ForkJoinPool?
public ForkJoinPool(int parallelism, ForkJoinWorkerThreadFactory factory,
UncaughtExceptionHandler handler, boolean asyncMode) {
this(checkParallelism(parallelism), checkFactory(factory), handler,
asyncMode ? FIFO_QUEUE : LIFO_QUEUE, "ForkJoinPool-" + nextPoolId() + "-worker-"); checkPermission(); } 複製程式碼
使用一個無限佇列來儲存所需要執行的任務,可以傳入執行緒的數量;不傳入,則預設使用當前計算機中可用的CPU數量;使用分治法來解決問題,使用 fork()和join()來進行呼叫。
3.建立一個可快取的執行緒池,可靈活回收空閒執行緒,若無可回收,則新建執行緒。
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(
0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } 複製程式碼
4.建立一個單執行緒的執行緒池。
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (
new ThreadPoolExecutor(
1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }複製程式碼
5.建立一個定長執行緒池,支援定時及週期性任務執行。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize); } 複製程式碼
三、原始碼分析
Executor結構:
Executor【介面】 一個執行新任務的簡單介面
public interface Executor{
void execute(Runnable command);
} 複製程式碼
ExecutorService【介面】 擴充套件了Executor介面。
新增了一些用來管理執行生命週期和任務生命週期的方法。
AbstractExecutorService【抽象類】
對ExecutorService介面的抽象類實現。不是我們分析的重點。 複製程式碼
ThreadPoolExecutor【類】
Java執行緒池的核心實現 複製程式碼
四、ThreadPoolExecutor
屬性解釋:
// AtomicInteger是原子類 ctlOf()返回值為RUNNING;private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));// 高3位表示執行緒狀態private static final int COUNT_BITS = Integer.SIZE - 3;// 低29位表示workerCount容量private static final int CAPACITY = (1 << COUNT_BITS) - 1;// runState is stored in the high-order bits// 能接收任務且能處理阻塞佇列中的任務private static final int RUNNING = -1 << COUNT_BITS;// 不能接收新任務,但可以處理佇列中的任務。private static final int SHUTDOWN = 0 << COUNT_BITS;// 不接收新任務,不處理佇列任務。private static final int STOP = 1 << COUNT_BITS;// 所有任務都終止private static final int TIDYING = 2 << COUNT_BITS;// 什麼都不做private static final int TERMINATED = 3 << COUNT_BITS;// 存放任務的阻塞佇列private final BlockingQueue<Runnable> workQueue;複製程式碼
值得注意的是狀態值越大執行緒越不活躍。
執行緒池狀態的轉換模型:
構造器:
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.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); 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、Callable一起講】
execute方法:
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); // clt記錄著runState和workerCount int c = ctl.get(); //workerCountOf方法取出低29位的值,表示當前活動的執行緒數 //然後拿執行緒數和 核心執行緒數做比較 if (workerCountOf(c) < corePoolSize) { // 如果活動執行緒數<核心執行緒數 // 新增到 //addWorker中的第二個參數列示限制新增執行緒的數量是根據corePoolSize來判斷還是maximumPoolSize來判斷 if (addWorker(command, true)) // 如果成功則返回 return; // 如果失敗則重新獲取 runState和 workerCount c = ctl.get(); } // 如果當前執行緒池是執行狀態並且任務新增到佇列成功 if (isRunning(c) && workQueue.offer(command)) { // 重新獲取 runState和 workerCount int recheck = ctl.get(); // 如果不是執行狀態並且 if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) //第一個引數為null,表示線上程池中建立一個執行緒,但不去啟動 // 第二個引數為false,將執行緒池的有限執行緒數量的上限設定為maximumPoolSize addWorker(null, false); } //再次呼叫addWorker方法,但第二個引數傳入為false,將執行緒池的有限執行緒數量的上限設定為maximumPoolSize else if (!addWorker(command, false)) //如果失敗則拒絕該任務 reject(command);}複製程式碼
總結一下它的工作流程:
- 當workerCount < corePoolSize,建立執行緒執行任務。
- 當workerCount >= corePoolSize && 阻塞佇列workQueue未滿,把新的任務放入阻塞佇列。
- 當workQueue已滿,並且workerCount >= corePoolSize,並且workerCount < maximumPoolSize,建立執行緒執行任務。
- 當workQueue已滿,workerCount >= maximumPoolSize,採取拒絕策略,預設拒絕策略是直接拋異常。
通過上面的execute方法可以看出,最主要的邏輯還是在addWorker方法中實現的。
addWorker方法:
主要工作就是線上程池中建立一個新的執行緒並執行。複製程式碼
引數定義:
- firstTask : the task the new thread should run first(or null if none).(指定新增執行緒執行的第一個任務或者不執行任務)
- core :if true use corePoolSize as bound,else maximumPoolSize.(core如果為true則使用corePoolSize繫結,否則為maximumPoolSize。)此處使用布林指示符而不是值,以確保在檢查其他狀態後讀取新值。
private boolean addWorker(Runnable firstTask, boolean core) { retry: for (;;) { int c = ctl.get(); // 獲取執行狀態 int rs = runStateOf(c); // Check if queue empty only if necessary. // 如果狀態值 >= SHUTDOWN (不接新任務&不處理佇列任務) // 並且 如果 !(rs為SHUTDOWN 且 firsTask為空 且 阻塞佇列不為空) if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) // 返回false return false; for (;;) { //獲取執行緒數wc int wc = workerCountOf(c); // 如果wc大與容量 || core如果為true表示根據corePoolSize來比較,否則為maximumPoolSize if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; // 增加workerCount(原子操作) if (compareAndIncrementWorkerCount(c)) // 如果增加成功,則跳出 break retry; // wc增加失敗,則再次獲取runState c = ctl.get(); // Re-read ctl // 如果當前的執行狀態不等於rs,說明狀態已被改變,返回重新執行 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 { // 根據firstTask來建立Worker物件 w = new Worker(firstTask); // 根據worker建立一個執行緒 final Thread t = w.thread; if (t != null) { // new一個鎖 final ReentrantLock mainLock = this.mainLock; // 加鎖 mainLock.lock(); try { // Recheck while holding lock. // Back out on ThreadFactory failure or if // shut down before lock acquired. // 獲取runState int rs = runStateOf(ctl.get()); // 如果rs小於SHUTDOWN(處於執行)或者(rs=SHUTDOWN && firstTask == null) // firstTask == null證明只新建執行緒而不執行任務 if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { // 如果t活著就拋異常 if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); // 否則加入worker(HashSet) //workers包含池中的所有工作執行緒。僅在持有mainLock時訪問。 workers.add(w); // 獲取工作執行緒數量 int s = workers.size(); //largestPoolSize記錄著執行緒池中出現過的最大執行緒數量 if (s > largestPoolSize) // 如果 s比它還要大,則將s賦值給它 largestPoolSize = s; // worker的新增工作狀態改為true workerAdded = true; } } finally { mainLock.unlock(); } // 如果worker的新增工作完成 if (workerAdded) { // 啟動執行緒 t.start(); // 修改執行緒啟動狀態 workerStarted = true; } } } finally { if (! workerStarted) addWorkerFailed(w); } // 返回線啟動狀態 return workerStarted;複製程式碼
為什麼需要持有mainLock?
因為workers是HashSet型別的,不能保證執行緒安全。
那w = new Worker(firstTask);如何理解呢
Work.java
private final class Worker extends AbstractQueuedSynchronizer implements Runnable複製程式碼
可以看到它整合AQS併發框架還發現了Runnable。證明它還是一個執行緒任務類。那我們呼叫t.start()事實上就是呼叫了該類重寫的run方法。
Worker為什麼使用ReentrantLock來實現呢?
tryAcquire方法它是不允許重入的,而ReentrantLock是允許重入的。對於執行緒來說,如果執行緒正在執行是不允許其他鎖重入進來的。
執行緒只需要兩個狀態,一個是獨佔鎖,表明是正在執行任務;一個是不加鎖,表明是空閒狀態。
public void run(){
runnWorker(this);
}複製程式碼
run方法有呼叫了runnWorker方法:
final void runWorker(Worker w) { // 拿到當前執行緒 Thread wt = Thread.currentThread(); // 拿到當前任務 Runnable task = w.firstTask; // 將Worker.firstTask置空 並且釋放鎖 w.firstTask = null; w.unlock(); // allow interrupts boolean completedAbruptly = true; try { // 如果task或者getTask不為空,則一直迴圈 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 // return ctl.get() >= stop // 如果執行緒池狀態>=STOP 或者 (執行緒中斷且執行緒池狀態>=STOP)且當前執行緒沒有中斷 // 其實就是保證兩點: // 1. 執行緒池沒有停止 // 2. 保證執行緒沒有中斷 if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) // 中斷當前執行緒 wt.interrupt(); try { // 空方法 beforeExecute(wt, task); Throwable thrown = null; try { // 執行run方法(Runable物件) 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置空, 完成任務++, 釋放鎖 task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { // 退出工作 processWorkerExit(w, completedAbruptly); }複製程式碼
總結一下runWorker方法的執行流程:
- while迴圈中,不斷的通過getTask方法從workerQueue中獲取任務
- 如果執行緒池正在停止,則中斷執行緒。否則呼叫3.
- 呼叫task.run()執行任務。
- 如果task為null則跳出迴圈,執行processWorkExit()方法,銷燬執行緒workers.remove(w).
這個流程圖非常經典:
除此之外,ThreadPoolExecutor還提供了tryAcquire、tryRelease、shutdown、shutdownNow、tryTerminate、等涉及的一系列執行緒狀態更改的方法。
在runWorker方法中,為什麼要在執行任務的時候對每個工作執行緒都加鎖呢?
shutdown方法與getTask方法存在競爭條件。