線程池的使用原理分析

線程池的使用原理分析

一、包含的知識點

  • 爲什麼需要線程池
  • 線程池API
  • 線程池創建方式
  • 線程池創建方式
  • 線程池ThreadPoolExecutor
  • 線程池原理分析
  • Callable/Future原理分析

二、爲什麼需要線程池

​ 線程是任務執行的基本單位, 但是計算機能夠執行的線程數是有一定限制的, 創建過多的線程會引起系統資源不足, 原因如下:

  • 線程的創建、銷燬需要消耗過多的系統資源
  • 線程上下文切換頻繁, 造成系統資源嚴重消耗

​ 爲了解決上面的問題, 提出了**線程池**概念, 那麼線程池具有哪些優點呢 ?

  • 降低創建線程、銷燬線程帶來的性能開銷
  • 合理的線程數,一定程度的提高了響應速度, 當有新任務需要執行時, 不需要等待線程創建可以立即執行
  • 合理的設置線程池大小, 避免線程數過多造成系統資源問題

三、線程池API

3.1 創建線程的方式

​ 在前面關於併發的文章中,有提到創建線程的方式, 這裏再提一下

  • 繼承Thread

    複寫run方法, 沒有返回值

  • 實現Runnable

    複寫run方法, 沒有返回值, 可以沒有run方法之外的其它方法

  • 實現Callbale、Future

    複寫call方法(類似run方法), 帶有返回值,

  • 線程池

    提供線程隊列, 隊列中包含所有等待狀態的線程, 減少了創建、銷燬開銷, 提高了響應速度

​ 這裏我們將講解使用線程池創建線程的方式

3.2 創建線程池的方式

​ 爲了方便對線程池的使用, Executors提供了創建線程池的工廠方法, 包含:

  • newSingleThreadExecutor, 創建線程數爲1的線程池, 若存在空閒線程則執行, 若不存在空閒線程, 則暫緩在任務隊列中
  • newFixedThreadPool,創建線程數爲固定大小的線程池, 任務提交時如果有空閒線程則立即執行,如果沒有空閒線程, 暫緩在任務隊列中, 等待空閒的線程再繼續執行。
  • newCachedThreadPool,根據實際情況調整線程數量的線程池, 最大線程池個數爲Integer.MAX_VALUE, 任務提交時, 如果有空閒的線程則執行任務, 如果無任務執行則不創建線程, 並且線程再空閒60s之後會自動回收**(可能引起創建線程過多的問題)**
  • newScheduledThreadPool, 創建指定線程數量的線程池, 其除了具有其它線程功能, 還帶有延遲、週期性執行任務的功能, 類似定時器。

3.3 線程統一封裝類型ThreadPoolExecutor

​ 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.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;
}

​ 從構造函數看出, ThreadPoolExecutor創建時沒有創建線程, 當有任務需要執行時纔會創建線程(線程的初始化和普通線程初始化方式一樣), 在完成任務後線程不會銷燬而是以掛起的狀態返回線程池, 直到應用程序再次發出請求, 掛起的線程會再次被使用, 這種方式節省了大量的系統資源開銷。

newFixedThreadPool

​ 創建線程數量只有一個的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定 順序(FIFO, LIFO, 優先級)執行

public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
  return new DelegatedScheduledExecutorService
    (new ScheduledThreadPoolExecutor(1));
}

public ScheduledThreadPoolExecutor(int corePoolSize) {
  super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
        new DelayedWorkQueue());
}

newFixedThreadPool

用途: newFixedThreadPool用於負載較大的服務器, 需要限制線程數量, 便於資源的合理利用

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory){
  return new ThreadPoolExecutor(nThreads, nThreads,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>(),
                                threadFactory);
}

newFixedThreadPool線程池的參數說明

  • corePoolSize、maximumPoolSize線程數都是指定的nThreads, 當線程數量大於核心線程數之後會放入阻塞隊列中
  • keepAliveTime=0, 超出核心線程數量外的線程存活時間爲0, 也就是立即銷燬
  • 阻塞隊列是LinkedBlockingQueue, 使用的是默認構造函數, 其容量大小是Integer,MAX_VALUE, 相對於沒有限制, 這可能導致過度創建任務, 造成系統資源問題

newFixedThreadPool執行流程

  • 執行線程數小於corePoolSize時,創建新線程執行任務
  • 線程數等於corePoolSize時, 將任務存入阻塞隊列中
  • 阻塞隊列LinkedBlockingQueue容量是無限制的, 新建的任務會一直存入阻塞隊列中
  • 執行線程會一直從阻塞隊列中獲取任務來執行

newCachedThreadPool

​ 原理: newCachedThreadPool線程池是一個可緩存的線程池, 如果線程池長度超過處理需要, 超過60s後會被回收, 如果沒有回收會繼續創建線程

public static ExecutorService newCachedThreadPool() {
  return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                60L, TimeUnit.SECONDS,
                                new SynchronousQueue<Runnable>());
}

newCachedThreadPool線程池的參數說明

  • corePoolSize核心線程數大小爲0, 直接向SynchronousQueue提交任務
  • maximumPoolSize最大線程數的大小爲Integer.MAX_VALUE, 緩存時間未過期內可任意創建線程, 這可能導致過度創建任務, 造成系統資源問題
  • 阻塞隊列SynchronousQueue, 不存儲元素的阻塞隊列, 每個put操作必須對應一個take操作, 否則不能繼續添加元素

newCachedThreadPool執行流程

  • 沒有核心線程數, 直接向SynchronousQueue提交任務
  • 如果有空閒線程, 取出任務執行, 如果沒有空閒線程, 新建一個新線程
  • 執行完任務的線程有60s的存活時間, 如果存活期間內沒有新的任務會被銷燬

四、線程池原理分析

​ 在進行原理分析之前, 再整理一下關鍵api的作用

  • Executers, 工具類, 主要用來創建線程池對象
  • ThreadPoolExecutor, 線程池的核心, 提供線程池的實現
  • ScheduledThreadPoolExecutor, 繼承自ThreadPoolExecutor, 支持定時、週期任務

4.1 線程執行入口execute

public void execute(Runnable command) {
  if (command == null) // 執行任務不能爲空, 如果爲空拋出異常
    throw new NullPointerException();
  int c = ctl.get();
  if (workerCountOf(c) < corePoolSize) { // 當前執行的線程數量比corePoolSize小, 新建線程來執行任務
    if (addWorker(command, true))
      return;
    c = ctl.get();
  }
  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); //如果線程已經被銷燬, 新建一個新的線程執行任務
  }
  else if (!addWorker(command, false)) //隊列已經滿, 創建一個新的線程執行任務
    reject(command); // 如果創建線程失敗, 拒絕任務(線程池已經滿了或線程池已經關閉)
}
  • 程序會先檢測任務是否爲空, 如果爲空拋出異常
  • 如果當前執行線程數 < 核心線程數, 創建一個新的線程
  • 如果當前執行線程數=核心線程數, 將任務添加到阻塞隊列, 任務添加到隊列後再次檢測是否需要添加新的線程
    • 如果線程處於非運行狀態, 且從阻塞隊列移除成功, 則拒絕該任務
    • 如果線程已經被銷燬了, 新建一個新的線程來執行任務
  • 如果核心線程數、阻塞隊列已滿, 則創建一個新的線程執行任務, 如果創建失敗說明線程池已經被關閉了或整個線程池已經滿了, 拒絕任務,

4.2 創建工作線程addWorker

​ 如果工作數小於核心線程數時, 調用addWorker創建新的線程執行任務

private boolean addWorker(Runnable firstTask, boolean core) {
  retry: //goto語句, 不常用的跳出循環方式, 避免死循環
  for (;;) {//核心是對worker線程數 + 1
    int c = ctl.get();
    int rs = runStateOf(c);

    // Check if queue empty only if necessary.
    /**
    * 1. 線程處於非運行狀態, 添加新任務, 拒絕
    * 2. SHUTDOWN狀態不接受新任務, 但是對已經在隊列中的任務仍然會執行; 
    * 如果狀態爲SHUTDOWN, firstTask任務==null, 任務隊列不爲空時, 是允許添加新線程的, 取反表示不滿足這個條件, 也就不允許添加worker
    */
    if (rs >= SHUTDOWN &&
        ! (rs == SHUTDOWN &&
           firstTask == null &&
           ! workQueue.isEmpty()))
      return false;

    for (;;) { // 自旋作用
      int wc = workerCountOf(c); // 獲得當前工作的線程數
      /**
      * 1. 如果工作線程大於默認的容量 CAPACITY = (1 << (Integer.SIZE - 3)) - 1, 不能繼續添加worker
      * 2. 如果工作線程大於核心線程數(core=true)或大於最大線程數(core=false), 不能繼續添加worker
      */
      if (wc >= CAPACITY ||
          wc >= (core ? corePoolSize : maximumPoolSize))
        return false;
      if (compareAndIncrementWorkerCount(c)) // 通過CAS設置工作線程數, 如果失敗跳到retry重試
        break retry;
      c = ctl.get();  // Re-read ctl
      if (runStateOf(c) != rs) //再次獲取狀態, 如果和歷史狀態不一樣,說明狀態發生了變更, 需要重試 
        continue retry;
      // else CAS failed due to workerCount change; retry inner loop
    }
  }

  // 核心是創建工作線程Worker
  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());
				/**
				* 滿足添加worker到workers集合的條件
				* 1. 線程池處於運行狀態
				*	2. 線程池處於SHUTDOWN, 且firstTask == null
				*/
        if (rs < SHUTDOWN ||
            (rs == SHUTDOWN && firstTask == null)) {
          //剛創建的線程還沒有start, 但是已經是alive狀態, 說明存在問題, 拋出異常
          if (t.isAlive()) // precheck that t is startable
            throw new IllegalThreadStateException();
          workers.add(w); // 添加worker線程到workers集合中
          int s = workers.size();
          if (s > largestPoolSize) //如果集合中工作線程數 > 最大線程數(largestPoolSize表示上次最大線程數), 更新最大線程數
            largestPoolSize = s;
          workerAdded = true; // 表示worker線程創建成功了
        }
      } finally {
        mainLock.unlock(); // 釋放持有的鎖
      }
      if (workerAdded) { // worker線程創建成功, 啓動線程, 並設置線程啓動狀態爲true
        t.start(); 
        workerStarted = true;
      }
    }
  } finally {
    if (! workerStarted)
      addWorkerFailed(w); //如果線程添加失敗, 移除剛創建worker, 和workers.add(w)相反的操作
  }
  return workerStarted; // 返回創建結果
}

Worker

​ 在上面分析addWorker代碼邏輯中, 創建Worker的邏輯如下,它僅僅是構造了一個Worker, 並且把firstTask封裝到worker中,

w = new Worker(firstTask);

​ 首先看下Worker源碼

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) {
      }
    }
  }
}
  • Worker是final修飾的類, 繼承自AQS類, 並且實現了Runnable接口 ,創建的每個worker都是一個線程, 包含的firstTask是初始化是被執行的任務

  • thread字段是真正處理task的核心線程, firstWorker需要執行的task

  • thread通過工廠方法創建, 以當前對象作爲參數

    this.thread = getThreadFactory().newThread(this);
    
  • run方法內部調用的是runWorker方法, 它是執行處理邏輯的核心方法

  • AQS的作用是實現了獨佔鎖的功能

    //1. 獲取鎖
    protected boolean tryAcquire(int unused) {
      if (compareAndSetState(0, 1)) {
        setExclusiveOwnerThread(Thread.currentThread());
        return true;
      }
      return false;
    }
    
    //2. 釋放鎖
    protected boolean tryRelease(int unused) {
      setExclusiveOwnerThread(null);
      setState(0);
      return true;
    }
    
    

爲什麼不使用ReentrantLock來實現呢?

​ addWorker方法有重入鎖的邏輯, Worker類自身繼承自AQS, 內部tryAcquire、tryRelease方法實現了獨佔鎖的邏輯, 那爲什麼ReentrantLock允許重入, 而tryAcquire不允許重入呢 ?

lock方法獲取了獨佔鎖之後,表示當前線程處於執行任務狀態中, 其具有下面的作用

  • 如果正在執行任務,則不應該中斷線程
  • 如果該線程現在不是獨佔鎖的狀態,也就是空閒的狀態,說明它沒有在處理任務,這時可以對該線程進行中斷
  • 線程池在執行 shutdown 方法或 tryTerminate 方法時會調用 interruptIdleWorkers 方法來 中斷空閒的線程,interruptIdleWorkers 方法會使用 tryLock 方法來判斷線程池中的線程 是否是空閒狀態
  • 之所以設置爲不可重入,是因爲我們不希望任務在調用像 setCorePoolSize 這樣的線程池 控制方法時重新獲取鎖,這樣會中斷正在運行的線程

addWorkerFailed

​ 如果Worker線程啓動失敗,在finally中做後續處理, 這個方法的主要作用是

  • 如果worker已經添加到workers集合中, 從集合中移除
  • CAS方式減少工作線程數
  • 嘗試結束線程池
private void addWorkerFailed(Worker w) {
  final ReentrantLock mainLock = this.mainLock;
  mainLock.lock();
  try {
    if (w != null)
      workers.remove(w);
    decrementWorkerCount();
    tryTerminate();
  } finally {
    mainLock.unlock();
  }
}

4.3 線程核心處理邏輯runWorker

final void runWorker(Worker w) {
  Thread wt = Thread.currentThread();
  Runnable task = w.firstTask;
  w.firstTask = null;
  /**
  * 表示當前worker線程允許中斷, new Worker()默認的state=-1, 而中斷需要state>=0, unlock的執行最後會調用Worker的tryRelease方法, 設置state=0, 保證interruptIfStarted方法可以正常執行
  */
  w.unlock(); // allow interrupts
  boolean completedAbruptly = true;
  try {
    // 如果task不爲空, 使用當前線程執行, 如果task爲空, 通過getTask()方法獲取任務
    // 這裏實現了線程複用功能
    while (task != null || (task = getTask()) != null) {
      //加鎖, 保證在shutDown()時不終止正在運行的worker
      w.lock();
      /** 
      * 因爲線程爲stop狀態時, 不接受新任務, 不執行已經加入任務隊列的任務, 
      * 需要對STOP及以上的狀態線程進行中斷
      * !wt.isInterrupted() 再一次檢查確保線程需要設置中斷標識位
      */
      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; // 置空task, 下次循環通過getTask()獲取新的task
        w.completedTasks++;
        w.unlock();
      }
    }
    completedAbruptly = false;
  } finally {
    /**
    * 1. 將woker從workers中移除
    * 2. completedAbruptly確定是否補充新的Worker進入workers
    */
    processWorkerExit(w, completedAbruptly);
  }
}

getTask

​ runWorker利用了線程複用功能, 執行task, task執行結束後會置空,然後通過getTask獲取新的任務進行執行,下面是獲取新task的邏輯

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.
    /**
    *	對線程狀態進行判斷,看是否需要對worker計數減1
    * 1. 線程狀態爲shutdown, 且workQueue爲空(shutdown狀態時如果workQueue不爲空, 需要繼續執行)
    * 2. 如果線程池狀態 >= stop
    */
    if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
      decrementWorkerCount();
      return null;
    }

    int wc = workerCountOf(c);

    // Are workers subject to culling?
    /**
    * timed變量用於控制判斷是否需要進行超時控制, 對於超過核心線程數量的線程, 需要進行超時控制
    *	1. allowCoreThreadTimeOut 默認false, 核心線程不允許進行超時
    * 2. wc > corePoolSize 表示當前線程池中線程數量 > 核心
    */
    boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
		/**
		*	1. 工作線程wc > maximumPoolSize可能是線程池運行時調用了 setMaximumPoolSize()方法改變了大小
		* 2. timed && timedOut 如果爲true, 表示當前操作需要進行超時控制, 並且上次從阻塞隊列中獲取任務發生了超時, 體現了空閒線程的存活時間
		*/
    if ((wc > maximumPoolSize || (timed && timedOut))
        && (wc > 1 || workQueue.isEmpty())) {
      if (compareAndDecrementWorkerCount(c))
        return null;
      continue;
    }

    try {
      // 根據timed來判斷, 如果timed爲true, 通過poll(time,unit)控制超時, 否則通過take阻塞的方式獲取隊列中任務
      Runnable r = timed ?
        workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
      workQueue.take();
      if (r != null)
        return r; // 如果拿到的任務不爲空, 返回獲取到的任務
      timedOut = true;
    } catch (InterruptedException retry) {
      timedOut = false; // 如果獲取任務時, 當前線程發生了中斷, 設置timeOUt=false, 繼續重試
    }
  }
}

​ 結合4.1節, 在執行 execute 方法時,如果當前線程池的線程數量超過了 corePoolSize 且小於 maximumPoolSize,並且 workQueue 已滿時,則可以增加工作線程,如果超時沒有獲取到任務,也就是 timedOut 爲 true 的情況,說明 workQueue 已經爲空了,也就說明了 當前線程池中不需要那麼多線程來執行任務了,可以把多於 corePoolSize 數量的線程銷燬掉,保持線程數量等於corePoolSize 即可。

processWorkerExit

​ runWorker 的 while 循環執行完畢以後,在 finally 中會調用 processWorkerExit,來銷燬工作線程

什麼時候會銷燬?

​ runWorker 方法執行完之後,也就是 Worker 中的 run 方法執行完,由 JVM 自動回收

4.4 拒絕策略

​ Executors創建線程池默認的拒絕策略是AbortPlocy, 也可以通過RejectedExecutionHandler handler參數直接指定, 拒絕策略包含下面幾種情況

private static final RejectedExecutionHandler defaultHandler =
        new AbortPolicy();
  • AbortPolicy:直接拋出異常,默認策略
  • CallerRunsPolicy:用調用者所在的線程來執行任務
  • DiscardOldestPolicy:丟棄阻塞隊列中最前的任務,並執行當前任務
  • DiscardPolicy:直接丟棄任務

4.5 線程池注意事項

  1. 線程池的構建不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式
	Executors 讓用戶不需要關心線程池的參數配置, 但是使用不當可能會帶來OOM問題。
	1)newFixdThreadPool 或者 singleThreadPool允許的隊列長度爲 Integer.MAX_VALUE,如果使用不當會導致大量請求堆積到隊列中導致 OOM 的風險。 
	2) newCachedThreadPool,允許創建線程數量爲 Integer.MAX_VALUE,這也可能會導致大量線程的創建出現 CPU 使用過高或者 OOM 的問題。
  1. 如何合理配置線程池的大小
1. 需要分析線程池執行的任務的特性: CPU 密集型還是IO密集型
2. 每個任務執行的平均時長,任務的執行時長是否涉及到網絡傳輸或底層系統資源
  • 如果是CPU密集型, 響應時間很快,CPU一直執行, 利用率很高, 線程數的配置應儘量根據系統核數相匹配
  • 如果是IO密集型函數, 主要是IO操作, CPU長時間處於空閒狀態, CPU利用率不高,可以通過計算時長來評估, CPU數目 * (線程池等待時長 + 線程池執行時長)/線程池執行時長
  1. 如何提前初始化線程

在3.3節中我們分析了創建ThreadPoolExecutor邏輯, 線程池創建之後, 線程池中沒有線程, 需要提交任務之後纔會創建線程。但是比較常見的情況是, 線程池創建之後需要立即創建線程,那麼有什麼方法可以立即創建線程呢 ?

  • prestartCoreThread(),初始化一個核心線程
  • prestartAllCoreThread(),初始化所有核心線程
  1. 動態調整線程池容量

ThreadPoolExecutor提供了動態調整線程池容量大小方法

  • setCorePoolSize(), 設置核心線程池大小
  • setMaximumPoolSize(), 設置最大線程數目
  1. beforeExecute、afterExecute的應用

五、Callable/Future原理分析

​ 在4.1 節中我們提到了執行任務的方法execute, 除了execute外, 還有submit方法用於執行任務, 那麼這兩種方法有什麼區別呢 ?

  • execute只接收Runnable作爲參數, submit可以接收Runnable、Callable作爲參數
  • execute如果出現問題會拋出異常, submit沒有異常拋出, 除非調用Future.get()
  • execute沒有返回值, submit有返回值, 返回值類型是Future

5.1 Callable/Future原理分析

Callable

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

​ Callable是一個函數式接口, 只有一個帶有返回值的call方法, 子類需要重寫這個方法。

FutureTask

​ 在進行講解前, 請先看下類繼承關係圖示

在這裏插入圖片描述

​ FutureTask實現了RunnableFuture接口,而RunnableFuture同時繼承了Runnable、Future接口, 這使得FutureTask不僅具有線程Runnable特性,也同時具有Future特性, 那麼Future具有什麼作用呢 ?

​ Future表示一個任務的生命週期, 並提供了相應方法來判斷是否完成或取消,也提供了方法獲取執行結果, 下面是類代碼

public interface Future<V> {
		//取消當前Future
    boolean cancel(boolean mayInterruptIfRunning);
		//判斷當前Future是否被取消, ture-取消, false-未取消
    boolean isCancelled();
		/**
    * 當前Future是否已結束,執行結束情況包含:
    * 運行完成、拋出異常、取消
    */
    boolean isDone();
		//獲取Future執行結果, 如果Future沒結束, 當前線程就等待, 直到Future結束, 再喚醒等待結果值的線程
    V get() throws InterruptedException, ExecutionException;
		//獲取Future的執行結果, 如果Future沒結束, 等待超時時間timeout
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

​ FutureTask使用了生產者消費者模型, Runnable屬於生產者, Future屬於消費者,Runnable通過run方法計算結果, Future通過get獲取結果, 如果生產者還沒有準備好, 消費者會被阻塞, 當生產者準備好後會喚醒消費者繼續執行。

5.2 FutureTask狀態分類

private volatile int state;
//NEW 新建狀態,表示這個 FutureTask 還沒有開始運行
private static final int NEW          = 0;
// COMPLETING 完成狀態, 表示 FutureTask 任務已經計算完畢了, 但是還有一些後續操作,例如喚醒等待線程操作,還沒有完成
private static final int COMPLETING   = 1;
// FutureTask任務完結,正常完成,沒有發生異常
private static final int NORMAL       = 2;
// FutureTask任務完結,因爲發生異常
private static final int EXCEPTIONAL  = 3;
// FutureTask任務完結,因爲取消任務
private static final int CANCELLED    = 4;
// FutureTask任務完結,也是取消任務,不過發起了中斷運行任務線程的中斷請求
private static final int INTERRUPTING = 5;
// FutureTask任務完結,也是取消任務,已經完成了中斷運行任務線程的中斷請求
private static final int INTERRUPTED  = 6;

5.3 run方法執行

public void run() {
  /**
  * 如果狀態state不是NEW, 或者設置runner值失敗,
  * 表示有別的線程在此之前調用 run 方法,併成功設置了 runner 值
  */
  if (state != NEW ||
      !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                   null, Thread.currentThread()))
    return;
  try {
    Callable<V> c = callable;
    if (c != null && state == NEW) { // 只有c不爲null且狀態state爲NEW的情況
      V result;
      boolean ran;
      try {
        result = c.call(); // 調用Callable的call方法執行邏輯
        ran = true;
      } catch (Throwable ex) {
        result = null;
        ran = false;
        setException(ex); // 設置異常結果
      }
      if (ran)
        set(result); // 無異常, 設置結果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);
  }
}

​ run方法實現是調用Callable的call方法, 返回結果值result, 根據是否發生異常, 調用set(result)或setException(ex)方法表示FutureTask完成

5.4 get方法執行

public V get() throws InterruptedException, ExecutionException {
  int s = state;
  if (s <= COMPLETING) // 判斷當前狀態 <= COMPLETING, 表示熱舞還沒有結束
    s = awaitDone(false, 0L); // 還沒有結束, 進行等待
  return report(s); // report返回結果
}


 public V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
   if (unit == null)
     throw new NullPointerException();
   int s = state;
   if (s <= COMPLETING &&
       (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
     throw new TimeoutException();
   return report(s);
 }

​ get方法是以阻塞的方式獲取執行結果

  • 判斷當前狀態, 如果狀態小於等於COMPLETING,表示FutureTask任務還沒有完結, 會調用awaitDone方法, 讓當前線程等待
  • report返回結果或者拋出異常

5.5 awaitDone方法執行

private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
  final long deadline = timed ? System.nanoTime() + nanos : 0L;
  WaitNode q = null;
  boolean queued = false; // queued表示節點是否已經添加, false-否, true-是
  for (;;) { // 自旋
    // 如果當前線程的中斷標識是true, 從列表移除節點q, 並拋出InterruptedException異常
    if (Thread.interrupted()) {
      removeWaiter(q);
      throw new InterruptedException();
    }

    int s = state;
    if (s > COMPLETING) { // 當狀態大於 COMPLETING 時,表示 FutureTask 任務已結束
      if (q != null)
        q.thread = null;
      return s;
    }
    else if (s == COMPLETING) // 表示還有一些後序操作沒有完成,當前線程讓出執行權
      Thread.yield();
    else if (q == null) // 表示狀態NEW,當前線程需要阻塞等待, 將它插入等待線程鏈表中
      q = new WaitNode();
    else if (!queued) // 使用CAS函數將新節點添加到鏈表中,如果添加失敗,那麼queued爲false, 下次循環時會繼續添加, 直到成功
      queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                           q.next = waiters, q);
    else if (timed) {// timed是bool值, 爲ture時表示需要設置超時情況
      nanos = deadline - System.nanoTime();
      if (nanos <= 0L) {
        removeWaiter(q);
        return state;
      }
      LockSupport.parkNanos(this, nanos); // 調用parkNanos方法等待nanos時間,被阻塞的線程在等到run方法執行結束後被喚醒
    }
    else
      LockSupport.park(this);
  }
}

​ 被阻塞的線程在等到run方法執行結束後被喚醒

5.6 report方法執行

​ report方法根據傳入狀態值s, 來決定是拋出異常還是返回結果值, 這兩個情況都是表示FutureTask完結了

private V report(int s) throws ExecutionException {
  Object x = outcome; // 表示call返回值
  if (s == NORMAL) // 表示正常完結狀態, 返回結果值
    return (V)x;
  /**
  *	>= CANCELLED, 表示手動取消FutureTask任務, 會拋出CancellationException異常
  */
  if (s >= CANCELLED)
    throw new CancellationException();
  // 如果不是上面的情況, 說明發生了異常, 這裏需要拋出異常
  throw new ExecutionException((Throwable)x);
}

六、線程池原理流程圖

​ 經過上面的分析, 這裏我們對線程池原理概括如下圖示

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章