Java中線程池ThreadPoolExecutor原理探究

http://ifeve.com/java%E4%B8%AD%E7%BA%BF%E7%A8%8B%E6%B1%A0threadpoolexecutor%E5%8E%9F%E7%90%86%E6%8E%A2%E7%A9%B6/

一、 前言

線程池主要解決兩個問題:一方面當執行大量異步任務時候線程池能夠提供較好的性能,,這是因爲使用線程池可以使每個任務的調用開銷減少(因爲線程池線程是可以複用的)。另一方面線程池提供了一種資源限制和管理的手段,比如當執行一系列任務時候對線程的管理,每個ThreadPoolExecutor也保留了一些基本的統計數據,比如當前線程池完成的任務數目。

另外,線程池提供許多可調參數和可擴展性鉤子。程序員可以使用更方便
工廠方法比如newCachedThreadPool(無限線程池,線程自動回收),newFixedThreadPool(固定大小的線程池)newSingleThreadExecutor(單個線程),當然用戶還可以自定義。

 

二、 類圖結構

ClassDiagram1.jpg

Executors其實是個工具類,裏面提供了好多靜態方法,根據用戶選擇返回不同的線程池實例。
ThreadPoolExecutor繼承了AbstractExecutorService,成員變量ctl是個Integer的原子變量用來記錄線程池狀態 和 線程池線程個數,類似於ReentrantReadWriteLock使用一個變量存放兩種信息。
Integer類型是32位二進制標示,其中高3位用來表示線程池狀態,後面 29位用來記錄線程池線程個數。

//用來標記線程池狀態(高3位),線程個數(低29位)
//默認是RUNNING狀態,線程個數爲0

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

//線程個數掩碼位數
private static final int COUNT_BITS = Integer.SIZE - 3;

//線程最大個數(低29位)00011111111111111111111111111111
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

//(高3位):11100000000000000000000000000000
private static final int RUNNING    = -1 << COUNT_BITS;

//(高3位):00000000000000000000000000000000
private static final int SHUTDOWN   =  0 << COUNT_BITS;

//(高3位):00100000000000000000000000000000
private static final int STOP       =  1 << COUNT_BITS;

//(高3位):01000000000000000000000000000000
private static final int TIDYING    =  2 << COUNT_BITS;

//(高3位):01100000000000000000000000000000
private static final int TERMINATED =  3 << COUNT_BITS;

// 獲取高三位 運行狀態
private static int runStateOf(int c)     { return c & ~CAPACITY; }

//獲取低29位 線程個數
private static int workerCountOf(int c)  { return c & CAPACITY; }

//計算ctl新值,線程狀態 與 線程個數
private static int ctlOf(int rs, int wc) { return rs | wc; }

<strong> 線程池狀態含義:</strong>

  • RUNNING:接受新任務並且處理阻塞隊列裏的任務
  • SHUTDOWN:拒絕新任務但是處理阻塞隊列裏的任務
  • STOP:拒絕新任務並且拋棄阻塞隊列裏的任務同時會中斷正在處理的任務
  • TIDYING:所有任務都執行完(包含阻塞隊列裏面任務)當前線程池活動線程爲0,將要調用terminated方法
  • TERMINATED:終止狀態。terminated方法調用完成以後的狀態

<strong> 線程池狀態轉換:</strong>

  • RUNNING -> SHUTDOWN
    顯式調用shutdown()方法,或者隱式調用了finalize(),它裏面調用了shutdown()方法。
  • RUNNING or SHUTDOWN)-> STOP
    顯式 shutdownNow()方法
  • SHUTDOWN -> TIDYING
    當線程池和任務隊列都爲空的時候
  • STOP -> TIDYING
    當線程池爲空的時候
  • TIDYING -> TERMINATED
    當 terminated() hook 方法執行完成時候

<strong> 線程池參數:</strong>

  • corePoolSize:線程池核心線程個數
  • workQueue:用於保存等待執行的任務的阻塞隊列。
    比如基於數組的有界ArrayBlockingQueue、,基於鏈表的無界LinkedBlockingQueue,最多隻有一個元素的同步隊列SynchronousQueue,優先級隊列PriorityBlockingQueue,具體可參考 https://www.atatech.org/articles/81568
  • maximunPoolSize:線程池最大線程數量。
  • ThreadFactory:創建線程的工廠
  • RejectedExecutionHandler:飽和策略,當隊列滿了並且線程個數達到maximunPoolSize後採取的策略,比如AbortPolicy(拋出異常),CallerRunsPolicy(使用調用者所在線程來運行任務),DiscardOldestPolicy(調用poll丟棄一個任務,執行當前任務),DiscardPolicy(默默丟棄,不拋出異常)
  • keeyAliveTime:存活時間。如果當前線程池中的線程數量比基本數量要多,並且是閒置狀態的話,這些閒置的線程能存活的最大時間
  • TimeUnit,存活時間的時間單位

<strong> 線程池類型:</strong>

  • newFixedThreadPool
    創建一個核心線程個數和最大線程個數都爲nThreads的線程池,並且阻塞隊列長度爲Integer.MAX_VALUE,keeyAliveTime=0說明只要線程個數比核心線程個數多並且當前空閒則回收。
   public static ExecutorService newFixedThreadPool(int nThreads) {
       return new ThreadPoolExecutor(nThreads, nThreads,
                                     0L, TimeUnit.MILLISECONDS,
                                     new LinkedBlockingQueue<Runnable>());
   }
//使用自定義線程創建工廠
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
       return new ThreadPoolExecutor(nThreads, nThreads,
                                     0L, TimeUnit.MILLISECONDS,
                                     new LinkedBlockingQueue<Runnable>(),
                                     threadFactory);
   }
  • newSingleThreadExecutor
    創建一個核心線程個數和最大線程個數都爲1的線程池,並且阻塞隊列長度爲Integer.MAX_VALUE,keeyAliveTime=0說明只要線程個數比核心線程個數多並且當前空閒則回收。

   public static ExecutorService newSingleThreadExecutor() {
       return new FinalizableDelegatedExecutorService
           (new ThreadPoolExecutor(1, 1,
                                   0L, TimeUnit.MILLISECONDS,
                                   new LinkedBlockingQueue<Runnable>()));
   }

   //使用自己的線程工廠
   public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
       return new FinalizableDelegatedExecutorService
           (new ThreadPoolExecutor(1, 1,
                                   0L, TimeUnit.MILLISECONDS,
                                   new LinkedBlockingQueue<Runnable>(),
                                   threadFactory));
   }
  • newCachedThreadPool
    創建一個按需創建線程的線程池,初始線程個數爲0,最多線程個數爲Integer.MAX_VALUE,並且阻塞隊列爲同步隊列,keeyAliveTime=60說明只要當前線程60s內空閒則回收。這個特殊在於加入到同步隊列的任務會被馬上被執行,同步隊列裏面最多隻有一個任務,並且存在後馬上會拿出執行。
  public static ExecutorService newCachedThreadPool() {
       return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                     60L, TimeUnit.SECONDS,
                                     new SynchronousQueue<Runnable>());
   }

   //使用自定義的線程工廠
   public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
       return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                     60L, TimeUnit.SECONDS,
                                     new SynchronousQueue<Runnable>(),
                                     threadFactory);
   }
  • newSingleThreadScheduledExecutor
    創建一個最小線程個數corePoolSize爲1,最大爲Integer.MAX_VALUE,阻塞隊列爲DelayedWorkQueue的線程池。

   public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
       return new DelegatedScheduledExecutorService
           (new ScheduledThreadPoolExecutor(1));
   }
  • newScheduledThreadPool
    創建一個最小線程個數corePoolSize,最大爲Integer.MAX_VALUE,阻塞隊列爲DelayedWorkQueue的線程池。
   public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
       return new ScheduledThreadPoolExecutor(corePoolSize);
   }

其中Worker繼承AQS和Runnable是具體承載任務的對象,Worker繼承了AQS自己實現了簡單的不可重入獨佔鎖,其中status=0標示鎖未被獲取狀態也就是未被鎖住的狀態,state=1標示鎖已經被獲取的狀態也就是鎖住的狀態。

DefaultThreadFactory是線程工廠,newThread方法是對線程的一個分組包裹,其中poolNumber是個靜態的原子變量,用來統計線程工廠的個數,threadNumber用來記錄每個線程工廠創建了多少線程。

三、 源碼分析

3.1 添加任務到線程池exectue方法



public void execute(Runnable command) {

   if (command == null)
       throw new NullPointerException();
  
   //獲取當前線程池的狀態+線程個數變量
   int c = ctl.get();

   //當前線程池線程個數是否小於corePoolSize,小於則開啓新線程運行
   if (workerCountOf(c) < corePoolSize) {
       if (addWorker(command, true))
           return;
       c = ctl.get();
   }

   //如果線程池處於RUNNING狀態,則添加任務到阻塞隊列
   if (isRunning(c) && workQueue.offer(command)) {

       //二次檢查
       int recheck = ctl.get();
       //如果當前線程池狀態不是RUNNING則從隊列刪除任務,並執行拒絕策略
       if (! isRunning(recheck) && remove(command))
           reject(command);

       //否者如果當前線程池線程空,則添加一個線程
       else if (workerCountOf(recheck) == 0)
           addWorker(null, false);
   }
   //如果隊列滿了,則新增線程,新增失敗則執行拒絕策略
   else if (!addWorker(command, false))
       reject(command);
}
  • 如果當前線程池線程個數小於corePoolSize則開啓新線程
  • 否則添加任務到任務隊列
  • 如果任務隊列滿了,則嘗試新開啓線程執行任務,如果線程個數>maximumPoolSize則執行拒絕策略。

重點看addWorkder方法:

private boolean addWorker(Runnable firstTask, boolean core) {
   retry:
   for (;;) {
       int c = ctl.get();
       int rs = runStateOf(c);

       // 檢查隊列是否只在必要時爲空.(1)
       if (rs >= SHUTDOWN &&
           ! (rs == SHUTDOWN &&
              firstTask == null &&
              ! workQueue.isEmpty()))
           return false;

       //循環cas增加線程個數
       for (;;) {
           int wc = workerCountOf(c);

           //如果線程個數超限則返回false
           if (wc >= CAPACITY ||
               wc >= (core ? corePoolSize : maximumPoolSize))
               return false;
           //cas增加線程個數,同時只有一個線程成功
           if (compareAndIncrementWorkerCount(c))
               break retry;
           //cas失敗了,則看線程池狀態是否變化了,變化則跳到外層循環重試重新獲取線程池狀態,否者內層循環重新cas。
           c = ctl.get();  // Re-read ctl
           if (runStateOf(c) != rs)
               continue retry;
       }
   }

   //到這裏說明cas成功了,(2)
   boolean workerStarted = false;
   boolean workerAdded = false;
   Worker w = null;
   try {
       //創建worker
       final ReentrantLock mainLock = this.mainLock;
       w = new Worker(firstTask);
       final Thread t = w.thread;
       if (t != null) {

           //加獨佔鎖,爲了workers同步,因爲可能多個線程調用了線程池的execute方法。
           mainLock.lock();
           try {
               
               //重新檢查線程池狀態,爲了避免在獲取鎖前調用了shutdown接口(3)
               int c = ctl.get();
               int rs = runStateOf(c);

               if (rs < SHUTDOWN ||
                   (rs == SHUTDOWN && firstTask == null)) {
                   if (t.isAlive()) // precheck that t is startable
                       throw new IllegalThreadStateException();
                   //添加任務
                   workers.add(w);
                   int s = workers.size();
                   if (s > largestPoolSize)
                       largestPoolSize = s;
                   workerAdded = true;
               }
           } finally {
               mainLock.unlock();
           }
           //添加成功則啓動任務
           if (workerAdded) {
               t.start();
               workerStarted = true;
           }
       }
   } finally {
       if (! workerStarted)
           addWorkerFailed(w);
   }
   return workerStarted;
}

代碼比較長,主要分兩部分,第一部分雙重循環目的是通過cas增加線程池線程個數,第二部分主要是併發安全的把任務添加到workers裏面,並且啓動任務執行。

先看第一部分的(1)

rs >= SHUTDOWN &&
              ! (rs == SHUTDOWN &&
                  firstTask == null &&
                  ! workQueue.isEmpty())

展開!運算後等價於

s >= SHUTDOWN &&
               (rs != SHUTDOWN ||
             firstTask != null ||
             workQueue.isEmpty())

也就是說下面幾種情況下會返回false:

  • 當前線程池狀態爲STOP,TIDYING,TERMINATED
  • 當前線程池狀態爲SHUTDOWN並且已經有了第一個任務
  • 當前線程池狀態爲SHUTDOWN並且任務隊列爲空

內層循環作用是使用cas增加線程個數,如果線程個數超限則返回false,否者進行cas,cas成功則退出雙循環,否者cas失敗了,要看當前線程池的狀態是否變化了,如果變了,則重新進入外層循環重新獲取線程池狀態,否者進入內層循環繼續進行cas嘗試。

到了第二部分說明CAS成功了,也就是說線程個數加一了,但是現在任務還沒開始執行,這裏使用全局的獨佔鎖來控制workers裏面添加任務,其實也可以使用併發安全的set,但是性能沒有獨佔鎖好(這個從註釋中知道的)。這裏需要注意的是要在獲取鎖後重新檢查線程池的狀態,這是因爲其他線程可可能在本方法獲取鎖前改變了線程池的狀態,比如調用了shutdown方法。添加成功則啓動任務執行。

3.2 工作線程Worker的執行

先看下構造函數:

Worker(Runnable firstTask) {
   setState(-1); // 在調用runWorker前禁止中斷
   this.firstTask = firstTask;
   this.thread = getThreadFactory().newThread(this);//創建一個線程
}

這裏添加一個新狀態-1是爲了避免當前線程worker線程被中斷,比如調用了線程池的shutdownNow,如果當前worker狀態>=0則會設置該線程的中斷標誌。這裏設置了-1所以條件不滿足就不會中斷該線程了。運行runWorker時候會調用unlock方法,該方法吧status變爲了0,所以這時候調用shutdownNow會中斷worker線程。

final void runWorker(Worker w) {
       Thread wt = Thread.currentThread();
       Runnable task = w.firstTask;
       w.firstTask = null;
       w.unlock(); // status設置爲0,允許中斷
       boolean completedAbruptly = true;
       try {
           while (task != null || (task = getTask()) != null) {
               
               w.lock();
               // 如果線程池當前狀態至少是stop,則設置中斷標誌;
               // 如果線程池當前狀態是RUNNININ,則重置中斷標誌,重置後需要重新
               //檢查下線程池狀態,因爲當重置中斷標誌時候,可能調用了線程池的shutdown方法
               //改變了線程池狀態。
               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;
                   //統計當前worker完成了多少個任務
                   w.completedTasks++;
                   w.unlock();
               }
           }
           completedAbruptly = false;
       } finally {

           //執行清了工作
           processWorkerExit(w, completedAbruptly);
       }
   }

如果當前task爲空,則直接執行,否者調用getTask從任務隊列獲取一個任務執行,如果任務隊列爲空,則worker退出。

private Runnable getTask() {
   boolean timedOut = false; // Did the last poll() time out?

   retry:
   for (;;) {
       int c = ctl.get();
       int rs = runStateOf(c);

       // 如果當前線程池狀態>=STOP 或者線程池狀態爲shutdown並且工作隊列爲空則,減少工作線程個數
       if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
           decrementWorkerCount();
           return null;
       }

       boolean timed;      // Are workers subject to culling?

       for (;;) {
           int wc = workerCountOf(c);
           timed = allowCoreThreadTimeOut || wc > corePoolSize;

           if (wc <= maximumPoolSize && ! (timedOut && timed))
               break;
           if (compareAndDecrementWorkerCount(c))
               return null;
           c = ctl.get();  // Re-read ctl
           if (runStateOf(c) != rs)
               continue retry;
           // else CAS failed due to workerCount change; retry inner loop
       }

       try {

           //根據timed選擇調用poll還是阻塞的take
           Runnable r = timed ?
               workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
               workQueue.take();
           if (r != null)
               return r;
           timedOut = true;
       } catch (InterruptedException retry) {
           timedOut = false;
       }
   }
}
private void processWorkerExit(Worker w, boolean completedAbruptly) {
   if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
       decrementWorkerCount();

   //統計整個線程池完成的任務個數
   final ReentrantLock mainLock = this.mainLock;
   mainLock.lock();
   try {
       completedTaskCount += w.completedTasks;
       workers.remove(w);
   } finally {
       mainLock.unlock();
   }

   //嘗試設置線程池狀態爲TERMINATED,如果當前是shutdonw狀態並且工作隊列爲空
   //或者當前是stop狀態當前線程池裏面沒有活動線程
   tryTerminate();

   //如果當前線程個數小於核心個數,則增加
   int c = ctl.get();
   if (runStateLessThan(c, STOP)) {
       if (!completedAbruptly) {
           int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
           if (min == 0 && ! workQueue.isEmpty())
               min = 1;
           if (workerCountOf(c) >= min)
               return; // replacement not needed
       }
       addWorker(null, false);
   }
}

3.3 shutdown操作

調用shutdown後,線程池就不會在接受新的任務了,但是工作隊列裏面的任務還是要執行的,但是該方法立刻返回的,並不等待隊列任務完成在返回。

public void shutdown() {
   final ReentrantLock mainLock = this.mainLock;
   mainLock.lock();
   try {
       //權限檢查
       checkShutdownAccess();

       //設置當前線程池狀態爲SHUTDOWN,如果已經是SHUTDOWN則直接返回
       advanceRunState(SHUTDOWN);

       //設置中斷標誌
       interruptIdleWorkers();
       onShutdown(); // hook for ScheduledThreadPoolExecutor
   } finally {
       mainLock.unlock();
   }
   //嘗試狀態變爲TERMINATED
   tryTerminate();
}

如果當前狀態>=targetState則直接返回,否者設置當前狀態爲targetState
private void advanceRunState(int targetState) {
   for (;;) {
       int c = ctl.get();
       if (runStateAtLeast(c, targetState) ||
           ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
           break;
   }
}

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

設置所有線程的中斷標誌,主要這裏首先加了全局鎖,同時只有一個線程可以調用shutdown時候設置中斷標誌,然後嘗試獲取worker自己的鎖,獲取成功則設置中斷標示
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();
   }
}

3.4 shutdownNow操作

調用shutdown後,線程池就不會在接受新的任務了,並且丟棄工作隊列裏面裏面的任務,正在執行的任務會被中斷,但是該方法立刻返回的,並不等待激活的任務執行完成在返回。返回隊列裏面的任務列表。

調用隊列的drainTo一次當前隊列的元素到taskList,
可能失敗,如果調用drainTo後隊列海不爲空,則循環刪除,並添加到taskList
public List<Runnable> shutdownNow() {


   List<Runnable> tasks;
   final ReentrantLock mainLock = this.mainLock;
   mainLock.lock();
   try {
       checkShutdownAccess();//權限檢查
       advanceRunState(STOP);// 設置線程池狀態爲stop
       interruptWorkers();//中斷線程
       tasks = drainQueue();//移動隊列任務到tasks
   } finally {
       mainLock.unlock();
   }
   tryTerminate();
   return tasks;
}

調用隊列的drainTo一次當前隊列的元素到taskList,
可能失敗,如果調用drainTo後隊列海不爲空,則循環刪除,並添加到taskList
private List<Runnable> drainQueue() {
   BlockingQueue<Runnable> q = workQueue;
   List<Runnable> taskList = new ArrayList<Runnable>();
   q.drainTo(taskList);
   if (!q.isEmpty()) {
       for (Runnable r : q.toArray(new Runnable[0])) {
           if (q.remove(r))
               taskList.add(r);
       }
   }
   return taskList;
}

3.5 awaitTermination操作

等待線程池狀態變爲TERMINATED則返回,或者時間超時。由於整個過程獨佔鎖,所以一般調用shutdown或者shutdownNow後使用。

   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();
       }
   }

四、總結

線程池巧妙的使用一個Integer類型原子變量來記錄線程池狀態和線程池線程個數,設計時候考慮到未來(2^29)-1個線程可能不夠用,到時只需要把原子變量變爲Long類型,然後掩碼位數變下就可以了,但是爲啥現在不一勞永逸的定義爲Long那,主要是考慮到使用int類型操作時候速度上比Long類型快些。

通過線程池狀態來控制任務的執行,每個worker線程可以處理多個任務,線程池通過線程的複用減少了線程創建和銷燬的開銷,通過使用任務隊列避免了線程的阻塞從而避免了線程調度和線程上下文切換的開銷。

 

另外需要注意的是調用shutdown方法作用僅僅是修改線程池狀態讓現在任務失敗並中斷當前線程,這個中斷並不是讓正在運行的線程終止,而是僅僅設置下線程的中斷標誌,如果線程內沒有使用中斷標誌做一些事情,那麼這個對線程沒有影響。

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