ThreadPoolExecutor源碼分析 -- execute、shutdown方法

原文鏈接:https://blog.csdn.net/seasonLai/article/details/82624236#commentBox

簡單介紹

  1. 來個簡單的例子
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 10,100, 
    TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10));
  executor.execute(() -> {
      System.out.println("hello world");
  });

ThreadPoolExecutor構造方法至少需要5個參數:corePoolSize(線程池的核心線程數),maximumPoolSize(線程池最大線程數),keepAliveTime(線程空閒存活時間),TimeUnit(存活時間的單位),BlockingQueue(阻塞隊列,用於存放Runnable);
另外兩個參數ThreadFactory(線程工廠,用於創建新線程),RejectedExecutionHandler(拒絕策略),不傳得話就會使用默認值。

  1. 首先來看ThreadPoolExecutor的繼承關係

在這裏插入圖片描述
3. 位於頂端的Executor接口很簡單

public interface Executor {
    void execute(Runnable command);
}
  1. ExecutorService增加了一些方法
    在這裏插入圖片描述
    5.AbstractExecutorService實現了一些方法
    在這裏插入圖片描述
  2. 開始分析ThreadPoolExecutor源碼,先來預先了解一些比較重要的成員變量、方法
    //記錄是運行狀態和線程數量
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    //線程允許的最大數量
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
    //運行狀態:可以接受新任務,並執行已經加入任務隊列的任務
    private static final int RUNNING    = -1 << COUNT_BITS;
    //關閉狀態:不接受新任務,但仍然執行已經加入任務隊列的任務
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    //停止狀態:不接受新任務,不執行已經加入任務隊列的任務,還中斷正在執行的任務
    private static final int STOP       =  1 << COUNT_BITS;
    //調整回收狀態:所有任務被停止,清空所有線程進入該狀態,然後執行terminated()方法
    private static final int TIDYING    =  2 << COUNT_BITS;
    //終止狀態:terminated()方法執行完成後進入該狀態
    private static final int TERMINATED =  3 << COUNT_BITS;

    //獲取線程池運行狀態(即取高三位)
    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; }

上面可以看到,線程池的運行狀態值和當前線程數量都存在了一個int類型數字(ctl)裏,高三位存的是狀態,其他位用於記錄數量。而且,狀態是隻能按照上面從上到下的順序變化的。

一、分析線程池執行過程
  1. 接着從execute()方法入手
public void execute(Runnable command) {
    if (command == null)//判空
        throw new NullPointerException();
    int c = ctl.get();//拿到線程池狀態
    if (workerCountOf(c) < corePoolSize) {
//核心線程數沒達到,添加一個核心線程
        if (addWorker(command, true))//成功就返回,否則繼續下面
            return;
//要麼是當前線程想添加一個核心線程的時候,核心線程數已經達到了;要麼是線程池狀態的原因,具體看下面addWorker方法
        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);//失敗了,說明線程池狀態原因或者隊列滿了
}
  1. 下面看addWorker方法是如何添加線程的
//第二個參數表示是否是核心線程,返回值表示是否添加線程成功
private boolean addWorker(Runnable firstTask, boolean core) {
   retry:
   for (;;) {
       int c = ctl.get();//拿到線程池的狀態
       int rs = runStateOf(c);//拿到運行狀態
       if (rs >= SHUTDOWN &&
           ! (rs == SHUTDOWN &&
              firstTask == null &&
              ! workQueue.isEmpty()))
           return false;
//這裏主要是理解! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty()),
//前面說過: SHUTDOWN狀態不接受新任務,但仍然執行已經加入任務隊列的任務,
//所以當進入SHUTDOWN狀態,而傳進來的任務爲空,並且任務隊列不爲空的時候,是允許添加新線程的,把這個條件取反就不允許了

       for (;;) {
           int wc = workerCountOf(c);//拿到線程數
           if (wc >= CAPACITY ||
               wc >= (core ? corePoolSize : maximumPoolSize))
               return false;//大於最大容量,或大於初始參數值返回false
           if (compareAndIncrementWorkerCount(c))
               break retry;//CAS添加線程數目成功,跳出最外層循環
           c = ctl.get();  // 重新拿到狀態
           if (runStateOf(c) != rs)
               continue retry;//如果運行狀態變了,進入外層循環
           //否則繼續在裏層循環嘗試CAS
       }
   }

//上面添加線程數量成功了,開始真正添加線程

   boolean workerStarted = false;
   boolean workerAdded = false;
   Worker w = null;
   try {
       w = new Worker(firstTask);//下面會分析Work類
       final Thread t = w.thread;
       if (t != null) {
           final ReentrantLock mainLock = this.mainLock;
           mainLock.lock();
           try {
               int rs = runStateOf(ctl.get());
               if (rs < SHUTDOWN ||
                   (rs == SHUTDOWN && firstTask == null)) {
//只有當前是正在運行狀態,或是SHUTDOWN且firstTask爲空,才進入
                   if (t.isAlive()) //這裏說明被調了start方法
                       throw new IllegalThreadStateException();
                   workers.add(w);//添加線程,workers是個HashSet,所以要加鎖保證線程安全
                   int s = workers.size();
                   if (s > largestPoolSize)
                       largestPoolSize = s;//記錄一下線程池的峯值
                   workerAdded = true;//添加線程成功,設置標誌位
               }
           } finally {
               mainLock.unlock();
           }
【標誌位1if (workerAdded) {
               t.start();//添加線程成功,開始運行線程
               workerStarted = true;//設置開始運行標誌位
           }
       }
   } finally {
       if (!workerStarted)
           addWorkerFailed(w);//這裏線程添加失敗
   }
   return workerStarted;
}
  1. 從上面可以大致猜測出,在這個線程池的設計中,線程被封裝成Worker的形式存在。
    下面分析一下Worker類
    在這裏插入圖片描述
    AbstractQueuedSynchronizer在前面ReentrantLock筆記(二) – 公平/非公平鎖源碼分析有分析過
    一開始看到它還實現了Runnable接口覺得有點奇怪,不知道有什麼用,接下去看就懂了。
final Thread thread;
//初始化的Runnable
Runnable firstTask;
//完成的任務數
volatile long completedTasks;
Worker(Runnable firstTask) {
    setState(-1); 
    this.firstTask = firstTask;
//這裏新建一個線程,注意傳入的是this,這個很關鍵
    this.thread = getThreadFactory().newThread(this);
}
public void run() {
    runWorker(this);
}

可以看到該類有一個線程成員,而這個線程的runnable卻是它自身,再看看它實現的run方法裏執行了runWorker方法。回顧一下上面addWorker方法的【標誌位1】處,那裏啓動了線程,所以會執行run方法,所以最終會調用runWorker方法。

runWorker方法是屬於ThreadPoolExecutor的方法

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是會阻塞的
      while (task != null || (task = getTask()) != null) {
          w.lock();
//前面說過,stop狀態時不接受新任務,不執行已經加入任務隊列的任務,還中斷正在執行的任務
//所以對於stop狀態以上是要中斷線程的
//(Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP)確保線程中斷標誌位爲true且是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;//置空,如果進入下一個循環可以繼續取任務
              w.completedTasks++;//完成數+1
              w.unlock();
          }
      }
      completedAbruptly = false;//說明不是用戶任務異常引起的
  } finally {
      processWorkerExit(w, completedAbruptly);
  }
}

到這裏基本就知道線程池如何複用線程,來執行任務了;也知道它怎麼控制線程最大數量,但還不知道如何控制在超過空閒時間時回收線程?答案就在getTask方法

下面的getTask方法有點重要,起到控制線程池線程數量的作用

private Runnable getTask() {
      boolean timedOut = false; // 取任務是否超時
      for (;;) {
          int c = ctl.get();
          int rs = runStateOf(c);
    //這個狀態判斷挺重要的,起到線程池關閉作用
          if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
              decrementWorkerCount();//線程數量減一
              return null;//這裏返回null,意味着一個線程會退出
          }
          int wc = workerCountOf(c);
    //這裏可以看出核心線程在空閒的時候也是可以設置被回收的
          boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
    //timed爲true將要有時間限制地取任務

          if ((wc > maximumPoolSize || (timed && timedOut))
              && (wc > 1 || workQueue.isEmpty())) {
        //大於最大限制線程數或超過空閒時間,並且當前線程數大於1或隊列爲空
              if (compareAndDecrementWorkerCount(c))
                  return null;//說明線程數減一成功,返回null,意味着一個線程會退出
              continue;//上面線程數減一失敗,說明線程數量已被搶先改變,繼續循環,
          }

          try {
              Runnable r = timed ?
                  workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                  workQueue.take();
              if (r != null)
                  return r;
              timedOut = true;//用於下一次循環中
          } catch (InterruptedException retry) {
              timedOut = false;
          }
      }
  }

上面的workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)用於超時檢查取任務,超過時間就會返回null,那timedOut就會變爲true,進入下一次循環,然後檢查是否可以減少線程數(timedOut就是其中一個條件),然後返回null就可以退出一個線程了;
PS: 上面有個很重要的判斷if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())),這個用於關閉線程池

二、分析線程池關閉過程

分析完線程池的執行流程,下面接着分析下線程池如何關閉,看shutdown方法

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();//【步驟1】對線程檢查一下,是否有權限修改
        advanceRunState(SHUTDOWN);//【步驟2】改變線程池狀態爲SHUTDOWN
        interruptIdleWorkers();//【步驟3】中斷所有線程
        onShutdown(); // 【步驟4】留給子類具體實現,如ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate();【步驟5}

上面分了五個步驟

【步驟2】:

//該方法改變線程池狀態爲SHUTDOWN或者STOP
private void advanceRunState(int targetState) {
    for (;;) {
        int c = ctl.get();
        if (runStateAtLeast(c, targetState) ||
            ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
            break;
    }
}

【步驟3】

private void interruptIdleWorkers() {
     interruptIdleWorkers(false);
 }

private void interruptIdleWorkers(boolean onlyOne) {
     final ReentrantLock mainLock = this.mainLock;
     mainLock.lock();
     try {
         for (Worker w : workers) {
             Thread t = w.thread;
             if (!t.isInterrupted() && w.tryLock()) {
                 try {
                     t.interrupt();//設置中斷標誌
                 } catch (SecurityException ignore) {
                 } finally {
                     w.unlock();
                 }
             }
             if (onlyOne)
                 break;
         }
     } finally {
         mainLock.unlock();
     }
 }

【步驟5】

final void tryTerminate() {
     for (;;) {
         int c = ctl.get();
         if (isRunning(c) ||
             runStateAtLeast(c, TIDYING) ||
             (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
             return;//狀態條件不滿足
         if (workerCountOf(c) != 0) {
             interruptIdleWorkers(ONLY_ONE);//線程數不爲0,終止一個線程
             return;
         }
         final ReentrantLock mainLock = this.mainLock;
         mainLock.lock();
         try {
             if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                 try {
                     terminated();//設置TIDYING狀態成功,調用terminated方法,具體由子類實現
                 } finally {
                     ctl.set(ctlOf(TERMINATED, 0));//進入TERMINATED狀態,說明已經關閉
                     termination.signalAll();//喚醒
                 }
                 return;
             }
         } finally {
             mainLock.unlock();
         }
//接着循環
     }
 }

看到termination.signalAll()的時候,有點疑惑,查了一下源碼中用到termination(一個ConditionObject實例)的地方

public boolean awaitTermination(long timeout, TimeUnit unit)
     throws InterruptedException {
     long nanos = unit.toNanos(timeout);
     final ReentrantLock mainLock = this.mainLock;
     mainLock.lock();
     try {
         for (;;) {
             if (runStateAtLeast(ctl.get(), TERMINATED))
                 return true;
             if (nanos <= 0)
                 return false;
             nanos = termination.awaitNanos(nanos);
         }
     } finally {
         mainLock.unlock();
     }
 }

是個公開的方法,聯想一下平常用法,該方法用於檢測線程池的關閉

    try{
        while(!executor.awaitTermination(500, TimeUnit.MILLISECONDS)) {
            //
        }
    } 
    catch (InterruptedException e) {
        //中斷處理
    }

從上面看出,shutdown方法改變狀態爲SHUTDOWN,並在嘗試給每個線程設置中斷標誌,接着結合getTask()方法返回null來停止移除線程,最後嘗試終止線程池。

參考:JDK1.8源碼

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