線程池:Executor框架源碼解析(下)

2.源碼解析

ThreadPoolExecutor類*

5)任務執行—execute方法

線程池在使用過程中提交任務使用的是submit方法,但是該方法本身不是 ThreadPoolExecutor類實現的,而是其父類 AbstractExecutorService類實現的,具體見筆記。該方法內部調用的 execute方法是需要子類自行實現。

execute方法的源碼如下,

// submit方法向execute方法中傳入的參數實際上是FutureTask對象
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();
    }
    
    // 工作的線程大於等於核心線程數,或者新建線程失敗
    // 線程池狀態正常,並且可以入隊的話,嘗試入隊列
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // 如果線程池狀態異常 嘗試從隊列中移除任務,可以移除的話就拒絕掉任務
        if (!isRunning(recheck) && remove(command))
            reject(command);
        // 發現可運行的線程數是 0,就初始化一個線程,這裏是個極限情況,入隊的時候,突然發現
        // 可用線程都被回收了
        else if (workerCountOf(recheck) == 0)
            // Runnable是空的,不會影響新增線程,但是線程在 start 的時候不會運行
            // Thread.run() 裏面有判斷
            addWorker(null, false);
    }
    
    // 隊列滿了,開啓線程到 maxSize,如果失敗直接拒絕,
    else if (!addWorker(command, false))
        reject(command);
}

AbstractExecutorService的submit方法向execute方法中傳入的實際上是newTaskFor方法創建的 FutureTask類對象,屬於Runnable的子類。

該方法分3步對傳入的任務進行處理,

  1. 如果工作線程數小於corePoolSize,嘗試創建新的線程並將FutureTask類對象傳入線程中。這也就解決了初始化時沒有在線程池中創建線程的問題。這一過程調用的是 ThreadPoolExecutor的addWorker方法,將任務加入到任務隊列中,該方法具體說明見下方筆記
  2. 任務被成功加入到任務隊列後,再次檢查線程池狀態(因爲最後一次檢查可能有線程已經死了)。如果isRunning方法返回爲 false,則從任務隊列中移除剛添加的任務並調用reject方法拒絕該任務。如果工作線程數爲0,則重新創建一個線程
  3. 如果無法將任務加入到workQueue,則嘗試調用addWorker方法,但不作爲核心線程。如果依舊無法添加,拒絕該task(此時因爲沒有加入到workQueue中,所以無需移除)

6)新建Worker並執行任務—addWorker方法

addWorker方法傳入參數說明,

private boolean addWorker(Runnable firstTask, boolean core)
參數 說明
firstTask submit -> newTaskFor -> execute -> addWorker,將最初的Runnable或Callable對象封裝得到的FutureTask對象
core 是否爲核心線程,true表示是核心線程;反之,爲非核心線程

addWorker方法的主要執行邏輯如下,分兩步進行解讀

更新Worker的數量

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

    // Check if queue empty only if necessary.
    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;
        if (compareAndIncrementWorkerCount(c))
            break retry;
        c = ctl.get();  // Re-read ctl
        if (runStateOf(c) != rs)
            continue retry;
        // else CAS failed due to workerCount change; retry inner loop
    }
}

retry是一個標記,和循環配合使用,continue retry 的時候,會跳到 retry處再次執行。如果 break retry,則跳出整個循環體。前

ThreadPoolExecutor 把狀態和線程池數量2個屬性存在了ctl變量中。

  1. 源碼中先檢查了線程池狀態,線程池狀態不正常返回 false
  2. 然後根據創建線程類型的不同(即是否是核心線程),進行數量的校驗。如果數量超過設定數目,返回false
  3. 通過 CAS方式更新狀 ctl,成功的話則跳出循環。否則再次取得線程池狀態,如果和上一次的狀態不一致,那麼從頭開始執行。如果狀態並未改變則繼續CAS更新 worker 的數量。

數目更新的流程參考下圖,
image

添加worker到workers集合中並且啓動worker持有的線程

該過程源碼如下,

boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
    w = new 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());

            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;
  1. 將FutureTask對象傳入到Worker中,創建新的worker對象
  2. 可以看到添加 worker 時需要先獲得鎖,確保併發安全
  3. 對線程池的狀態進行檢驗。如果線程池狀態不對,則調用addWorkFailed方法,解鎖並返回false
  4. 進一步檢驗線程是否能夠被啓動,如果不能,則拋出IllegalThreadStateException異常並執行addWorkFailed方法,結束執行前解鎖
  5. 將worker加入到workers集合中,調用 worker 中封裝的線程的 start 方法啓動線程。

上述過程失敗調用的 addWorkerFailed 方法是對之前操作進行回滾的方法。

7)線程任務實際運行—runWorker方法

該方法代碼如下,

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    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.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);
    }
}

上述代碼具體流程總結如下,

  1. 取出worker中的 firstTask(FutureTask類對象)賦值給變量task,並使Worker對象的firstTask指向 null
  2. 如果沒有firstTask,則調用getTask方法從workQueue中獲取task
  3. 獲取 worker對象的鎖,因爲Worker本身是AQS的子類,所以具有鎖的特性
  4. 執行beforeExecute方法,該方法在ThreadPoolExecutor中是空方法,如有需要在子類實現
  5. 執行task.run,調用FutureTask類的run方法。FutureTask的run方法參見筆記,無論是Runnable還是Callable都能夠執行
  6. 執行afterExecute方法,同樣在ThreadPoolExecutor中是空方法,如有需要在子類實現
  7. 內層finally代碼塊中清空task,worker完成的任務數目+1,釋放鎖
  8. 當有異常或者沒有 task 可執行時,進入到外層 finally 代碼塊中調用 processWorkerExit 方法退出當前 worker。從 workers 中移除本 worker 後,如果 worker 數量小於 corePoolSize,則創建新的 worker,以維持 corePoolSize 大小的線程數。

下面這行代碼

while (task != null || (task = getTask()) != null)

確保了 worker 不停地從 workQueue 中取得 task 執行。getTask 方法會從 workQueue 中 調用poll 或者 take 取出其中的 task。

8)獲取workQueue的任務—getTask方法

從任務隊列中獲取Runnable對象,

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

        int wc = workerCountOf(c);

        // true 運行的線程數大於 coreSize || 核心線程也可以被滅亡
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        // 隊列以 LinkedBlockingQueue 爲例,timedOut 爲 true 的話說明下面 poll 方法執行返回的是 null
        // 說明在等待 keepAliveTime 時間後,隊列中仍然沒有數據
        // 說明此線程已經空閒了 keepAliveTime 了
        // 再加上 wc > 1 || workQueue.isEmpty() 的判斷
        // 所以使用 compareAndDecrementWorkerCount 方法使線程池數量減少 1
        // 並且直接 return,return 之後,此空閒的線程會自動被回收
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            // 從隊列中阻塞拿 worker
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            // 設置已超時,說明此時隊列沒有數據
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

代碼兩處關鍵說明,

  1. 使用隊列的 polltake方法從隊列中拿數據,根據隊列的特性,隊列中有任務可以返回,隊列中無任務會阻塞
  2. 方法中的第二個 if 判斷,說的是在滿足一定條件下(條件看註釋),會減少空閒的線程,減少的手段是使可用線程數減一,並且直接 return null,說明該線程空閒超過時間限制且當前無任務可執行,可銷燬該線程。返回後線程結束執行,JVM 會自動回收該線程

3.任務拒絕策略

execute方法在線程池已滿且任務隊列已滿的條件下會發生線程池拒絕執行任務的情況,調用的是ThreadPoolExecutor的reject方法,該方法源碼如下,

// 方法的參數是傳入execute方法的FutureTask類對象
final void reject(Runnable command) {
    handler.rejectedExecution(command, this);
}

上面代碼中的handler對象是初始化過程中指定的拒絕執行處理類,在 ThreadPoolExecutor中實現了4種拒絕執行處理類。這4類拒絕執行類均實現了 RejectedExecutionHandler接口。

RejectedExecutionHandler接口

該接口中只定義了一個方法,

public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

ThreadPoolExecutor內部拒絕執行類

public static class AbortPolicy implements RejectedExecutionHandler {
    public AbortPolicy() { }

    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString());
    }
}

public static class CallerRunsPolicy implements RejectedExecutionHandler {
    public CallerRunsPolicy() { }

    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            r.run();
        }
    }
}

public static class DiscardPolicy implements RejectedExecutionHandler {
    public DiscardPolicy() { }

    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    }
}

public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    public DiscardOldestPolicy() { }

    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            e.getQueue().poll();
            e.execute(r);
        }
    }
}
類名 說明
AbortPolicy 當線程池和隊列都滿時,再有任務進來直接拋出RejectedExecutionException異常,是默認的拒絕處理類
CallerRunsPolicy 當線程池和隊列都滿時,任務將會被任務的調用方線程執行(如主線程),如果線程池關閉,那麼任務將會被拋棄
DiscardPolicy 當線程池和隊列都滿時,再有任務進來時,直接將任務拋棄且不會有任何返回結果
DiscardOldestPolicy 當線程池和隊列都滿時,再有任務進來,拋棄最老的未處理的任務,然後重試該新進來的任務,如果線程池關閉,那麼任務將會被拋棄

使用示例

public static void main(String [] args) throws ExecutionException, InterruptedException {
	// 創建一個線程池,分別指定不同的拒絕處理類
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,1,
                                                                    500,
                                                                    TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1),

                                                                    new ThreadPoolExecutor.CallerRunsPolicy()
                                                                    //new ThreadPoolExecutor.DiscardPolicy()
                                                                    //new ThreadPoolExecutor.DiscardOldestPolicy()
                                                                    //new ThreadPoolExecutor.AbortPolicy()
                                                                    );
	// 提交第1個任務
    Future<String> taskOne = threadPoolExecutor.submit(()->{
        System.out.println("taskOne start...");
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("taskOne 沉睡2秒結束...");
        return Thread.currentThread().getName();
    });

	// 提交第2個任務
    Future<String> taskTwo = threadPoolExecutor.submit(()->{
        System.out.println("taskTwo start...");
        return Thread.currentThread().getName();
    });

	// 提交第3個任務
    Future<String> taskThree = threadPoolExecutor.submit(()->{
        System.out.println("taskThree start...");
        return Thread.currentThread().getName();
    });

    System.out.println("taskOne:" + taskOne.get());
    System.out.println("taskTwo:" + taskTwo.get());
    System.out.println("taskThree:" + taskThree.get());
    threadPoolExecutor.shutdown();
}

上述例子中創建了一個核心線程池數與最大線程池數都爲1,阻塞隊列長度也爲1的線程池。然後啓動三個任務,其中第一個任務執行等待2秒。

taskOne 佔用了線程池中的唯一的線程,taskTwo 進入阻塞隊列,這時隊列已滿,taskThree再進入線程池觸發對應的拒絕策略。

  • 使用AbortPolicy策略

Exception in thread “main” java.util.concurrent.RejectedExecutionException: …
taskOne start…
taskOne 沉睡2秒結束…
taskTwo start…

主線程在taskThree的submit()時拋出異常,後面的信息都不打印了

  • 使用CallerRunsPolicy策略

taskOne start…
taskThree start…
taskOne 沉睡2秒結束…
taskOne:pool-1-thread-1
taskTwo start…
taskTwo:pool-1-thread-1
taskThree:main

可以看出taskOnetaskTwo的執行都是線程池內的那個線程完成的,而taskThree的執行線程則是調用taskThree的main函數主線程

  • 使用DiscardPolicy策略

taskOne start…
taskOne 沉睡2秒結束…
taskOne:pool-1-thread-1
taskTwo start…
taskTwo:pool-1-thread-1

默默拋棄策略不再打印taskThree 相關信息,並且程序在taskThree.get()處阻塞,線程池遲遲無法關閉

  • 使用DiscardOldestPolicy策略

taskOne start…
taskOne 沉睡2秒結束…
taskOne:pool-1-thread-1
taskThree start…

taskThree再進入線程池時,線程池根據策略把未執行的taskTwo給拋棄了,然後執行了taskThree,所以打印了 “taskThree start…” 。但是,因爲taskTwo 已被拋棄所以在調用taskTwo.get()時發生了阻塞。所以沒有打印main方法中執行後的相關的信息,且線程池始終無法關閉。

總結

1)submit方法整體流程

image

2)線程池整體圖

image

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