執行緒池原始碼分析

Createsequence發表於2021-02-16

一、問什麼要使用執行緒池?

 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);}複製程式碼

總結一下它的工作流程:

  1. 當workerCount < corePoolSize,建立執行緒執行任務。
  2. 當workerCount >= corePoolSize && 阻塞佇列workQueue未滿,把新的任務放入阻塞佇列。
  3. 當workQueue已滿,並且workerCount >= corePoolSize,並且workerCount < maximumPoolSize,建立執行緒執行任務。
  4. 當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方法的執行流程:

  1. while迴圈中,不斷的通過getTask方法從workerQueue中獲取任務
  2. 如果執行緒池正在停止,則中斷執行緒。否則呼叫3.
  3. 呼叫task.run()執行任務。
  4. 如果task為null則跳出迴圈,執行processWorkExit()方法,銷燬執行緒workers.remove(w).

這個流程圖非常經典:

執行緒池原始碼分析

除此之外,ThreadPoolExecutor還提供了tryAcquire、tryRelease、shutdown、shutdownNow、tryTerminate、等涉及的一系列執行緒狀態更改的方法。


在runWorker方法中,為什麼要在執行任務的時候對每個工作執行緒都加鎖呢?

shutdown方法與getTask方法存在競爭條件。


相關文章