追蹤解析 ThreadPoolExecutor 原始碼

三流發表於2019-01-19

零 前期準備

0 FBI WARNING

文章異常囉嗦且繞彎。

1 版本

JDK 版本 : OpenJDK 11.0.1

IDE : idea 2018.3

2 ThreadPoolExecutor 簡介

ThreadPoolExecutor 是 jdk4 中加入的工具,被封裝在 jdk 自帶的 Executors 框架中,是 java 中最經典的執行緒池技術。

ThreadPoolExecutor 類在 concurrent 包下,和其它執行緒工具類一樣都由 Doug Lea 大神操刀完成。

[ 在看完 Spring ioc 和 Gson 之後有點乏了,換換口味看一些 jdk 的原始碼 ]

3 Demo

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo {

    public static void main(String[] args){
        //建立執行緒池
        //這裡使用固定執行緒數的執行緒池,執行緒數為 5
        ExecutorService executorService = Executors.newFixedThreadPool(5);

        for(int i = 0 ; i < 100 ; i ++){
            final int ii = i;
            //建立 Runnable 作為執行緒池的任務
            Runnable r = () -> System.out.println(ii);
            //執行
            executorService.execute(r);
        }
    }
}

一 執行緒池的初始化

執行緒池的初始化呼叫的 Executors 框架的靜態方法:

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

繼續追蹤這個構造方法:

//ThreadPoolExecutor.class
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
            Executors.defaultThreadFactory(), defaultHandler);
}

繼續追蹤:

//ThreadPoolExecutor.class
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;
    //用於儲存任務的佇列
    //此處使用 LinkedBlockingQueue 來儲存任務,其執行緒安全
    this.workQueue = workQueue;
    //keepAliveTime 引數用於表示:
    //對於超出執行緒和佇列快取總和的任務,是否要臨時增加執行緒來處理
    //超出的執行緒的存在時間是多少
    //這裡使用的是定長執行緒池,所以 keepAliveTime = 0,即不增加執行緒
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    //用於建立執行緒的工廠類
    this.threadFactory = threadFactory;
    //handler 用來處理 task 太多時候的拒絕策略
    //此例中使用的是預設的,即定義在 ThreadPoolExecutor 中的 defaultHandler 物件
    this.handler = handler;
}

二 Worker

Worker 是 ThreadPoolExecutor 的內部類,可以看做是 Runnable 的代理類:

//ThreadPoolExecutor.class
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
    
    private static final long serialVersionUID = 6138294804551838833L;
    final Thread thread;
    Runnable firstTask;
    //完成 task 數量的計數器
    volatile long completedTasks;

    Worker(Runnable firstTask) {
        //這個方法是 AbstractQueuedSynchronizer 中的方法,功能相當於加鎖
        //-1 的意思是後續的任務會處於阻塞狀態,即為已經加鎖
        setState(-1);
        //在建立的時候存入一個要處理的 task
        //需要注意的是每個 worker 物件被建立出來之後是可以重複利用來處理多個 task 的
        this.firstTask = firstTask;
        //worker 會用自身作為 Runnable 物件去建立一個執行緒
        //這裡呼叫執行緒工廠進行執行緒建立
        this.thread = getThreadFactory().newThread(this);
    }

    //對於執行緒變數來說,其啟動的就是 worker 的 run() 方法
    public void run() {
        //runWorker(...) 方法在 ThreadPoolExecutor 裡
        runWorker(this);
    }

    //獲取鎖的狀態
    protected boolean isHeldExclusively() {
        return getState() != 0;
    }
    //重寫了 AbstractQueuedSynchronizer 中的 tryAcquire(...) 方法
    //嘗試加鎖
    protected boolean tryAcquire(int unused) {
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }
    //重寫了 AbstractQueuedSynchronizer 中的 tryRelease(...) 方法
    //嘗試釋放鎖
    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) {
            }
        }
    }
}

追蹤一下 runWorker(…) 方法:

//ThreadPoolExecutor.class
final void runWorker(Worker w) {
    //獲取當前所在的執行緒的例項物件
    Thread wt = Thread.currentThread();
    //獲取 task
    Runnable task = w.firstTask;
    //取出來之後把 task 置空
    w.firstTask = null;
    //此處釋放鎖
    w.unlock();
    //指示器,此變數為 true 的時候確認該方法已經執行完畢
    boolean completedAbruptly = true;
    try {
        //此處為一個 while 迴圈,用於不斷的執行 task
        //getTask() 方法會從佇列裡不斷抓取 task 並進行執行
        //當 task 為 null,且佇列裡已經沒有更多 task 的時候,就會終止迴圈
        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(...) 和 afterExecute(...) 方法在 ThreadPoolExecutor 中並沒有實現
                //是預留出來給使用者重寫,以達到業務需求的方法
                beforeExecute(wt, task);
                try {
                    //此處執行 task
                    task.run();
                    afterExecute(task, null);
                } catch (Throwable ex) {
                    afterExecute(task, ex);
                    throw ex;
                }
            } finally {
                //將執行的 task 置空
                task = null;
                //每完成一個 task 就會加 1
                w.completedTasks++;
                //釋放鎖
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        //這個方法會銷燬掉 worker
        //同時如果檢測到有新的 task 又會重新建立 Worker
        processWorkerExit(w, completedAbruptly);
    }
}

Worker 是執行緒池中真正起完成業務邏輯的元件,是任務和執行緒的封裝。

三 執行緒池的狀態控制

執行緒池的狀態主要由 ctl 變數來進行控制:

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

ctl 是一個 AtomicInteger 型別的變數,其實可以簡單理解為一個 int 值,AtomicInteger 只是能夠適應高併發的原子化操作的需要。

ctl 的前 29 位數用來表示執行緒(Worker)的數量,後面三位用來表示執行緒池的狀態。

執行緒池的狀態有五種,分別是 Running、Shutdown、Stop、Tidying、Terminate,根據單詞就能猜出大概。

注意的是,這五種狀態線上程池中都以 int 變數的形式存在,從前到後依次變大,對狀態的比較有一系列方法:

//ThreadPoolExecutor.class
private static boolean runStateLessThan(int c, int s) {
    //c 的狀態值要小於 s
    return c < s;
}
//ThreadPoolExecutor.class
private static boolean runStateAtLeast(int c, int s) {
    //c 的狀態值要大於或等於 s
    return c >= s;
}
//ThreadPoolExecutor.class
private static boolean isRunning(int c) {
    //狀態裡只有 RUNNING 是小於 SHUTDOWN 的
    return c < SHUTDOWN;
}

在這些方法裡,傳入的引數 c 一般指的是當前執行緒池狀態,s 是用來對比的參照狀態。

四 執行緒池的執行

該 part 的起點:

executorService.execute(r);

來追蹤 execute(…) 方法:

public void execute(Runnable command) {
    //有效性驗證
    if (command == null)
        throw new NullPointerException();
    
    //ctl 是一個 AtomicInteger 型別的變數,用來記錄執行緒池的狀態
    int c = ctl.get();
    
    //workerCountOf(...) 方法會返回當前執行的 Worker 的數量
    if (workerCountOf(c) < corePoolSize) {
        //Worker 的數量小於執行緒池容量的情況下
        //直接增加 Worker 並取出 task 去執行
        if (addWorker(command, true))
            return;
        //如果 Worker 已經順利執行了 task,應該會直接返回掉
        //如果執行中出現了其它情況,則會繼續往下走
        //此處重新整理狀態
        c = ctl.get();
    }
    //當 Worker 數量已經達到執行緒池的指定數量,或者新增 Worker 的時候出問題的時候,會進入此判斷語句
    //先判斷執行緒池是否處於活躍狀態,且 task 是否已經被成功新增到佇列中
    //如果不滿足,會進入 else 語句中,先最後嘗試一次 addWorker(...) 方法,如果不成功就拒絕 task
    //reject(...) 方法會呼叫 handler 的拒絕策略
    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 reject

這裡先提及一下 reject(…) 方法:

//ThreadPoolExecutor.class
final void reject(Runnable command) {
    handler.rejectedExecution(command, this);
}

本質是呼叫了 handler 物件的相關方法。在本例中,handler 物件指向了 defaultHandler:

//ThreadPoolExecutor.class
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();

defaultHandler 是一個 AbortPolicy 型別的物件,而 AbortPolicy 是 ThreadPoolExecutor 的靜態內部類。

AbortPolicy 起作用的方法為 rejectedExecution(…) 方法:

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

也就是說,在 task 過多的情況下,AbortPolicy 的應對策略是丟擲異常。

2 addWorker

來看一下核心方法 addWorker(…):

//ThreadPoolExecutor.class
private boolean addWorker(Runnable firstTask, boolean core) {
    //先標記這個 for 迴圈,方便退出迴圈
    retry:
    //在每一次迴圈開始之前會重新整理一次狀態標識
    for (int c = ctl.get();;) {
        //這裡先進行判斷,如果執行緒池已經關閉了,或者沒有 task 了,就會返回 false
        if (runStateAtLeast(c, SHUTDOWN)
            && (runStateAtLeast(c, STOP)
                || firstTask != null
                || workQueue.isEmpty()))
            return false;

        for (;;) {
            //如果 Worker 數量已經超出了最大值就會直接返回 false
            if (workerCountOf(c)
                >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
                return false;
            //將 ctl 變數的值加 1,如果成功了就會跳出迴圈
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();
            //在狀態值比 SHUTDOWN 大的時候會直接跳到最外頭的迴圈裡
            //需要注意的是最外面的 for 迴圈會判斷狀態值是否大於 SHUTDOWN
            //如果大於 SHUTDOWN 的話就返回 false 了
            if (runStateAtLeast(c, SHUTDOWN))
                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 c = ctl.get();
                //如果狀態值 c 是 RUNNING,或者 [c 是 RUNNING 或者 SHUTDOWN 且 firstTask 是 null] 就會進入這個判斷語句
                //
                if (isRunning(c) || (runStateLessThan(c, STOP) && firstTask == null)) {
                    //如果這個執行緒已經處於運作狀態,會丟擲異常
                    if (t.isAlive())
                        throw new IllegalThreadStateException();
                    //workers 是一個列表,用於儲存 Worker 物件
                    workers.add(w);
                    //獲取 Worker 的數量
                    int s = workers.size();
                    //largestPoolSize 用來記錄執行緒池達到過的最大執行緒數
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    //標記 Worker 已經被新增
                    workerAdded = true;
                }
            } finally {
                //釋放鎖
                mainLock.unlock();
            }
            //先判斷 Worker 是否已經被新增到 workers 內了
            if (workerAdded) {
                //這是該方法核心的啟動執行緒方法
                t.start();
                //標記 Worker 已經開始執行了
                workerStarted = true;
            }
        }
    } finally {
        //如果沒有標記 Worker 已經開始工作,會在這裡銷燬掉 Worker
        if (!workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

五 一點嘮叨

先總結一下執行緒池的業務邏輯:

1 接收到 task (即實現了 Runnable 介面的例項物件) [execute(...) 方法]

2 用 task 去嘗試建立一個 Worker 例項 [execute(...) 方法]
    2.1 如果 Worker 數量沒有達到執行緒池的指定最大值 -> 新建
    2.2 如果 Worker 數量達到了執行緒池的指定最大值 -> 不會再建立,而是把 task 儲存起來等待空閒的 Worker 去提取
    2.3 如果 task 佇列也已經滿了,無法再新增 -> 觸發拒絕機制(handler)

3 Worker 在執行的時候呼叫其內部的 Thread 例項物件的 start() 方法 [addWorker(...) 方法]

4 該 start() 方法會呼叫到 Worker 的 run() 方法 [Worker.class 內的 run() 方法]

5 Worker 的 run() 方法本質上是封裝了 task 的 run() 方法 [runWorker(...) 方法]

主線業務邏輯不算複雜,比較艱難的是為了保證資料的一致性,執行緒池程式碼中充斥著大量的狀態判斷和鎖機制。

並且為了考慮效能問題,執行緒池的設計沒有使用悲觀鎖(synchronized 關鍵字),而是大量使用了 ASQ 和 ReetrentLock 機制。


本文僅為個人的學習筆記,可能存在錯誤或者表述不清的地方,有緣補充

相關文章