簡單聊一聊ThreadPoolExecutor

EumJi發表於2018-02-01

本文首發於www.eumji025.com

簡介

執行緒池的誕生於JDK1.5,主要的目的是解決我們在使用執行緒的時候通常都是重複的建立和銷燬,為了讓執行緒能夠得到複用,避免我們重複的建立和銷燬,提高我們的效率,降低記憶體的開銷。沒錯又是Doug Lea大神又搞出了執行緒池這一強力工具。

我們最熟悉的執行緒池使用案例應該就是資料庫連線池,以及我們任務排程都是會使用執行緒池的。

簡單聊一聊ThreadPoolExecutor

Executors用來建立和管理我們具體的ThreadPoolExecutor,這裡使用了典型的設計模式 - 工廠模式。ThreadPoolExecutor是真正執行緒池,繼承了AbstractExecutorService類,Java集合類和併發類都大量的使用了抽象類實現部分通用的功能。此處的AbstractExecutorService就實現了ExecutorService部分介面功能。最關鍵的execute方法交給子類去實現。和集合類的套路基本上是一模一樣。

看一下Executors的具體實現。

public static ExecutorService newFixedThreadPool(int var0) {
    return new ThreadPoolExecutor(var0, var0, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory var0) {
    return new Executors.FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), var0));
}
public static ScheduledExecutorService newScheduledThreadPool(int var0) {
    return new ScheduledThreadPoolExecutor(var0);
}
複製程式碼

隨便列舉了其中的幾個例子,這裡具體描述一下建構函式的幾個引數作用。

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {
複製程式碼

corePoolSize => 指代預設建立的執行緒數。

maximumPoolSize => 建立執行緒的最大數量。

keepAliveTime => 執行緒存活時間

unit => 存活的時間,應該都很熟悉,包含日,時,分,秒等

workQueue => 存放執行緒的阻塞佇列

threadFactory => 建立執行緒的工廠,預設為DefaultThreadFactory,主要是重寫ThreadFactory介面的newThread的方法。

handler => 拒絕策略,主要是指工作任務超過了workQueue的大小後,該執行哪種策略進行處理。主要有一下幾種:

1.AbortPolicy => 預設的策略,直接丟擲異常

2.DiscardPolicy => 放棄被拒絕的任務,其實就是啥也不幹

3.DiscardOldestPolicy => 放棄最老的任務,也就是馬上要執行的任務

4.CallerRunsPolicy => 直接執行被放棄的任務,個人不喜歡,赤裸裸的插隊(而且根本就沒有拒絕)

上面簡單的介紹了執行緒池的各個引數,現在就看一下到底可以生成哪些執行緒池。

fixedThreadPool

fixedThreadPool => 固定大小執行緒池,一旦建立,數量就不會再改變,如果任務超過執行緒的數量,就會進入等待的佇列,使用的LinkedBlockingQueue就可以認為是無界的佇列了因為capacity等於Integer.MAX_VALUE

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
複製程式碼

我們簡單的測試一下就可以發現其中的功能

static class MyThread extends Thread{
    @Override
    public void run() {
      try {
        Thread.sleep(500L);
        System.out.println(Thread.currentThread().getName() +" running !!!");
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
}
static void fixedThreadPoolTest(){
    ExecutorService executorService = Executors.newFixedThreadPool(5);
    for (int i = 0; i < 20; i++) {
        executorService.submit(new MyThread());
    }
}
複製程式碼

執行這個test方法的時候,會發現只會有5種執行緒名稱被列印。說明沒有沒有獲得執行緒的任務就等待,而且是複用的。後續的例子都將使用MyThread做測試。

cachedThreadPool

newCachedThreadPool => 大小不固定,為達到最大值時可以動態生成執行緒,預設使用的是SynchronousQueue佇列,是一種同步佇列,指只能存放一個元素,新增了必須被消費了才能再新增。

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

下面簡單的使用一個例子進行說明。

static void cachedThreadPoolTest() throws InterruptedException {
    ExecutorService executorService = Executors.newCachedThreadPool();

    for (int i = 0; i < 5; i++) {
      executorService.submit(new MyThread());
    }
    Thread.sleep(1000L);
    for (int i = 0; i < 20; i++) {
      executorService.submit(new MyThread());
    }
}
複製程式碼

上面測試的例子將複用前五個執行緒,並再新建15個執行緒,結果就不展示了。

singleThreadExecutor

singleThreadExecutor => 大小固定的且只有一個執行緒的執行緒池,可以理解為一個元素的fixedThreadPool。

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

就不進行測試程式碼的展示了,因為和fixedThreadPool的道理相同,只不過只有一個執行緒。

scheduledThreadPool

scheduledThreadPool => 是一種大小不固定的定時任務執行緒池。使用的DelayedWorkQueue延時佇列進行任務記錄。DelayedWorkQueue是ScheduledThreadPoolExecutor的內部類

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}
複製程式碼

下面演示一個簡單的例子演示如何。需要注意的是延時任務呼叫的方法會有點不同。

static void singleThreadScheduledTest(){
    ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
    for (int i = 0; i < 2; i++) {
      //延遲5秒執行,只執行一次
      executorService.schedule(new MyThread(),1,TimeUnit.SECONDS);
      //延遲5秒 執行5個週期執行
      //executorService.scheduleAtFixedRate(new MyThread(),5,3, TimeUnit.SECONDS);
    }
}
複製程式碼

上面測試裡的1代表延時1秒執行,且只執行一次,如果想週期執行,可呼叫下面註釋的方法scheduleAtFixedRate方法,表示第一次延時5秒執行,後面的是以3秒為一個週期的執行。

需要注意的是:

1.不會自動出現停止,除非發生異常或者手動的取消掉。

2.假如執行的週期比執行緒的執行時間短,則會以延時的任務的執行時間長度為準。

singleThreadScheduledExecutor

public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
    return new DelegatedScheduledExecutorService
      (new ScheduledThreadPoolExecutor(1));
}
複製程式碼

可以看出來singleThreadScheduledExecutor和ScheduledThreadPoolExecutor還是有一定的區別的,singleThreadScheduledExecutor是單獨的一個實現類,不過本文不做具體分析。

方法解讀

上面大概的介紹了執行緒池中的幾種執行緒池,接下來我們將介紹一下如何其中到底是如何實現的。我們只說一下常規的執行緒池的執行邏輯。

從上面的程式碼我們可以看到,各種執行緒池的不同主要體現線上程的數量範圍和使用的workQueue不同。最終都會呼叫submit方法,首先看一下執行緒池中幾種不同引數的submit方法。

public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null);
}
public <T> Future<T> submit(Runnable task, T result) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task, result);
}
public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}
複製程式碼

submit方法支援多種型別的任務,最終都會包裝成RunnableFuture的task。這裡體現了一個重要的設計模式 - 介面卡模式,下面看一下詳細程式碼

protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
    return new FutureTask<T>(runnable, value);
}
public FutureTask(Runnable runnable, V result) {
    this.callable = Executors.callable(runnable, result);
    this.state = NEW;       // ensure visibility of callable
}
public static <T> Callable<T> callable(Runnable task, T result) {
    if (task == null)
        throw new NullPointerException();
    return new RunnableAdapter<T>(task, result);
}
複製程式碼

其實目的很簡單就是把Runnable最終包裝成Callable。

在介紹具體的方法之前,首先我們看一下執行緒池中幾種狀態,因為後續會使用到。

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));	//最小的負數
    private static final int COUNT_BITS = Integer.SIZE - 3;
    public static final int SIZE = 32;
	private static final int CAPACITY   = (1 << COUNT_BITS) - 1;//29個1

    // runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS; //11100000000000000000000000000000
    private static final int SHUTDOWN   =  0 << COUNT_BITS;//0
    private static final int STOP       =  1 << COUNT_BITS;//100000000000000000000000000000
    private static final int TIDYING    =  2 << COUNT_BITS;//1000000000000000000000000000000
    private static final int TERMINATED =  3 << COUNT_BITS;//1100000000000000000000000000000

    // c & 29個0 其實就是獲取高三位
    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; }

複製程式碼

這是一個非常經典的設計,我們可以看出,我們的低29位都是用來記錄任務的。高3位表示狀態

RUNNING => 高3位值是111。 此狀態表示可以接受新任務

SHUTDOWN => 高3位值是000。 此狀態不能接受新任務,但會繼續已有的任務

STOP => 高3位值是001。 此狀態的執行緒不接受也不處理任務,並且會中斷處理中的任務

TIDYING => 高3位值是010。 執行緒池所有的任務都已經終止,並且worker數量為0,即將執行terminated方法

TERMINATED => 高3位值是011。在TIDYING狀態上,已經執行了terminated方法,執行緒池完全的停止。

上面的這些數字很重要,一定要記住。

接著上面的講,最終不管是哪種submit的方法,都會交給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;
      c = ctl.get();
    }
   //否則判斷執行緒池的狀態,如果是正在執行狀態,加入到workQueue等待執行
    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); //直接開啟一個執行緒,因為任務已經在workQueue上了
    }
  //如果workQueue新增失敗,嘗試直接起一個worker,用於coreSize和MaxSize不等的情況
    else if (!addWorker(command, false))
      reject(command);
}
複製程式碼

簡單的概述一下上述程式碼所做的事情:

1.如果當前的活動的執行緒小於設定的執行緒數,則直接啟動新執行緒執行任務,否則

2.如果執行緒池是處於執行狀態,且執行緒數為corePoolSize,且workQueue沒滿,把任務加入到等待佇列中,如果執行成功,再次檢查執行緒的執行狀態,到第三步,否則到第四步

3.再次校驗狀態,如果沒有處於執行的狀態,把新增的任務剔除。

4.執行緒池如果不處於執行狀態,或者workQueue已經滿了,workQueue滿了,還可以再次嘗試執行分配一個執行緒(用於corePoolSize不等於maximumPoolSize的情況下),如果還是失敗,說明執行緒池已經到極限了和或者是已經關閉了執行緒池。

接下來需要看一下第一步中的addWorker方法

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

            // 執行緒的狀態,running狀態的無法進入
           //需要注意其中不能新增一個woker的條件。SHUTDOWN狀態下且不同時滿足firstTask為null,workQueue為空的條件
            if (rs >= SHUTDOWN &&! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty()))
                return false;
            //思考能夠到達這裡的條件
            for (;;) {
               //執行緒的數量
                int wc = workerCountOf(c);
               //超出了最大容量,或者執行緒超過規定的數量,失敗
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
               //數量+1,跳出迴圈
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get(); 
                if (runStateOf(c) != rs)
                    continue retry;
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
           //新增一個worker 裡面有玄機,後面介紹
            w = new Worker(firstTask);
            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)) {
                        if (t.isAlive()) // 如果執行緒已經在執行中,
                            throw new IllegalThreadStateException();
                      //新增一個worker記錄
                        workers.add(w);
                        int s = workers.size();
                       //增加最大數量 ,預設為0
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
              // 新增成功則啟動任務
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }
複製程式碼

addWorker方法看起來比較長,其實做的事情非常簡單。

1.判斷執行緒池的狀態,如果不是正常狀態(rs >= SHUTDOWN &&! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty()))則新增失敗,否則進行第二步

2.根據條件判斷是否還能在新增執行緒,可以則workCount加1成功跳出迴圈,執行worker邏輯,否則重試或者結束。

3.第二步成功,配置Worker,並在此檢查執行緒池的狀態,如果沒有問題,則設定worker相關資訊,並啟動執行緒。

然後我們在來看一下execute方法中的remove方法,我相信remove方法不僅僅是從workQueue移除元素,不然也不會單獨寫個方法。

public boolean remove(Runnable task) {
    boolean removed = workQueue.remove(task);
    tryTerminate(); // In case SHUTDOWN and now empty
    return removed;
}
複製程式碼

首先從workQueue移除元素,然後嘗試關閉執行緒池。具體邏輯還是在tryTerminate方法中。

final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            //如果是執行狀態,或者已經停止,或者是存於shutdown狀態,但是任務沒有處理完,都直接結束,也就證明嘗試停止失敗
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;
           //如果還有工作的執行緒,把worker的中斷狀態設為true,ONLY_ONE表示只中斷一個
            if (workerCountOf(c) != 0) { // Eligible to terminate
                interruptIdleWorkers(ONLY_ONE);
                return;
            }
			//沒有工作的執行緒了真的藥停止了
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
              //設定為TIDYING狀態
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                      //留給子類擴充套件的
                        terminated();
                    } finally {
                       //最終設定為TERMINATED狀態
                        ctl.set(ctlOf(TERMINATED, 0));
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
        }
    }
複製程式碼

tryTerminate是任何從workerQueue移除任務都會呼叫的方法,用來判斷當前執行緒池是否已經沒有活著的執行緒了,如果沒有了就關閉執行緒池。

再次回到execute方法中,reject方法就是呼叫拒絕策略中的rejectedExecution方法,預設的AbortPolicy就是拋個異常僅此而已。

後面也只是一些相同的方法就不再多介紹了,最重要的還是條件判斷。

shutdown方法

在看一下執行緒池的shutdown相關的方法。

主要包含三個方法:

1.shutdown方法 => 將執行緒池的狀態設定為shutdown狀態

2.shutdownNow方法 => 直接停止執行緒池。

3.isShutdown方法 => 判斷當前執行緒池的狀態是否不是running狀態

下面跟隨著原始碼分別看看這幾個方法的詳情。

public boolean isShutdown() {
  	return ! isRunning(ctl.get());
}
private static boolean isRunning(int c) {
  	return c < SHUTDOWN;
}
複製程式碼

isShutdown方法還是老套路,直接判斷是否小於SHUTDOWN狀態就可以判斷是否為Running狀態。

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
      checkShutdownAccess(); //檢查許可權,
      advanceRunState(SHUTDOWN); //設定為SHUTDOWN狀態
      interruptIdleWorkers(); //中斷等待任務的執行緒
      onShutdown(); // 空方法,為ScheduledThreadPoolExecutor留的方法
    } finally {
      mainLock.unlock();
    }
    tryTerminate(); //嘗試關閉執行緒池
}
複製程式碼

shutdown方法是將執行緒池的狀態設定為SHUTDOWN,並且設定執行緒的中斷狀態,注意這裡的中斷只會中斷在等待中的執行緒(沒有上鎖的執行緒)。比較簡單裡面的詳情就不展示出來了。

 public List<Runnable> shutdownNow() {
     List<Runnable> tasks;
     final ReentrantLock mainLock = this.mainLock;
     mainLock.lock();
     try {
       checkShutdownAccess();
       advanceRunState(STOP); //設定為STOP狀態
       interruptWorkers(); //中斷所有執行中的且沒有被設定中斷標誌的執行緒
       tasks = drainQueue(); //獲取等待中的任務列表
     } finally {
       mainLock.unlock();
     }
     tryTerminate();
     return tasks;
 }
複製程式碼

shutdownNow方法和上面的shutdown方法很相似,只是不同的是,shutdownNow更徹底,直接將執行緒池的狀態設定為STOP,並且會移除有所有的等待中的task,而且這裡設定的是所有執行中執行緒的中斷狀態。下面看一下drainQueue方法

 private List<Runnable> drainQueue() {
     BlockingQueue<Runnable> q = workQueue;
     ArrayList<Runnable> taskList = new ArrayList<Runnable>();
     q.drainTo(taskList);//將workQueue的物件轉移到taskList(會清空q裡的元素)
     if (!q.isEmpty()) {
       //如果q還有新offer的元素
       for (Runnable r : q.toArray(new Runnable[0])) {
         if (q.remove(r))
           taskList.add(r); //新增到taskList中
       }
     }
     return taskList;
 }
複製程式碼

主要做的事情就是從workQueue中獲取所有的任務放到taskList中,並從workQueue中刪除。

補充

前面我們瞭解執行緒是如何執行任務和關閉執行緒池的方法,但是我們需要思考這樣一個場景,就是當我們有任務被放在workQueue裡的時候,上述的方法並沒有講述這樣的情況下是如何執行的,這裡需要介紹一下其中的邏輯。這時候就可以看一下留在addWorker的玄機了。。。

 private final class Worker extends AbstractQueuedSynchronizer implements Runnable
Worker(Runnable firstTask) {
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);
}
複製程式碼

可以從建構函式看出,thread物件其實指向的就是當前的worker,所以addWorker方法後面的thread.start就會呼叫worker.run方法。還有一點值得注意的是Worker繼承了AbstractQueuedSynchronizer,下面詳細看一下run方法中的實現

public void run() {
  	runWorker(this);
}
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask; //取出worker中的任務
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
      //獲取任務,
        while (task != null || (task = getTask()) != null) {
            w.lock();
           //如果執行緒池停止了,當前 執行緒沒有終端,將當前執行緒設為中斷狀態
            if ((runStateAtLeast(ctl.get(), STOP) ||(Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted())
                wt.interrupt();
            try {
              //空方法,留給子類的
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                   //執行task的邏輯
                    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++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}
複製程式碼

從上面的方法可以看出,worker的run方法主要做了幾件事。

1.迴圈獲取任務,並執行,發生異常則丟擲異常

2.如果沒有問題最終關閉worker。

首先看一下getTask方法是如何操作的。

private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

      //檢查佇列是否為空,或者執行緒池是否處理關閉狀態
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
           //遞減worker的數量
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // 判斷是否已經設定超時
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
       //執行緒超過了最大數量或者超時,說明不可用了,幹掉
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return 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;
        }
    }
}
複製程式碼

上述獲取任務的方法還是比較複雜的,特別是狀態的判斷,簡單的總結一下:

1.如果執行緒池的狀態是STOP或者工作的佇列為空,迴圈去一個一個的減少worker的數量,此處只是減少數量。並沒有結束裡面的worker。

2.如果不滿足第一條,開始校驗是否設定了超時關閉執行緒或者說執行緒數超過了設定的值。這時候判斷去判斷執行緒1.是否超過了執行緒數的最大值或者滿足了超時的條件 2.執行緒數大於1或者已經沒有待處理的工作了。滿足這些條件就去掉一個worker

3.如果2也沒有滿足,就嘗試獲取task,獲取到了就返回,否則就設定timeOut為true,說明取task失敗了。

上面介紹瞭如何獲取任務和管理worker的getTask方法,下面我們在看一下任務執行完後的processWorkerExit方法。

private void processWorkerExit(Worker w, boolean completedAbruptly) {
   //如果completedAbruptly為true就減少worker的數量,產生於runWorker發生異常。
    if (completedAbruptly) decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks;
      //刪除這個worker
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }
    //完成任務了就嘗試關閉執行緒池
    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
        addWorker(null, false);
    }
}
複製程式碼

上述的方法主要是worker進行退出,主要做的幾件事如下

1.判斷是否正常的結束,如果不是就要刪減worker。

2.記錄完成任務的數量並移除worker。

3.嘗試關閉執行緒池,然後判斷執行緒池的狀態,如果還沒有處於停止的狀態,繼續判斷是不是正常的結束,如果是的話去檢查執行緒池裡執行緒的狀態,如果正常就結束,如果不滿足最好都新增一個worker。

總結

1.本文首先介紹了執行緒池的幾種型別的執行緒池,從程式碼都可以看到其實共用用的同一個構造方法,不同的只是引數的不用。

2.分析了執行緒池的幾種狀態,這裡是比較重要的,特別是高三位表示狀態,低29位表示執行緒數。

3.分析了submit和execute方法,通過corePoolSize,maximumPoolSize,workQueue來判斷新任務是新啟一個執行緒還是加入到workQueue中,或是執行拒絕策略。

4.分析了shutdown相關的方法邏輯

5.分析了worker到底是如何工作的,這裡主要是對內部類worker的run及其涉及的方法進行解讀。

本篇只是個人看原始碼的一些觀點,如果存在不清晰或者表述錯誤的觀點歡迎大家反饋。

與君共勉!!!

相關文章