【詳解】ThreadPoolExecutor原始碼閱讀(一)

貓毛·波拿巴發表於2018-11-01

系列目錄

工作原理簡介

  ThreadPoolExecutor會建立一組工作執行緒,每當一個工作執行緒完成其任務的時候,會向任務佇列獲取新的任務執行。如果任務佇列為空,獲取任務的執行緒將被阻塞。不出意外的話,工作執行緒會一直工作,直到執行緒池主動釋放空閒執行緒,或者隨著執行緒池的終結而結束。

    

工作者執行緒

在ThreadPoolExecutor中有一個內部類Worker,但這個Woker類並沒有像想象中的那樣繼承於Thread,而是通過組合的方式繫結一個執行緒。在一定程度上,也可以把這個Worker看作是一個工作者執行緒

可能是由於想要使用AbstractQueuedSynchronizer的功能吧,Java的類不支援多繼承,就只好採取組合的方式來處理了

 

這個Worker如何與一個執行緒繫結?

這個工作者任務是在建立的時候與一個執行緒繫結的,其通過外部類ThreadPoolExecutor提供的執行緒工廠,建立一個執行緒,把自己傳遞給它,並保留執行緒的引用。

Worker(Runnable firstTask) {
    //防止在runWorker之前被中斷,因為worker一旦建立就會加入workers集合中
    //其他執行緒可能會中斷空閒執行緒
    //而空閒執行緒的依據就是能否獲得worker的鎖
    setState(-1); 
    //設定初始任務,注意這裡沒有null檢查,故初始任務可以為空
    this.firstTask = firstTask;
    //通過ThreadPoolExecutor的提供執行緒工廠來建立執行緒,並把自身賦值給它,作為其執行緒任務
    //保留執行緒引用,用於中斷執行緒
    this.thread = getThreadFactory().newThread(this);
}

Worker繫結的執行緒何時啟動?

至此,執行緒的建立和繫結完成了(這裡的執行緒指的只是Java的Thread物件),但是還沒見到執行緒的啟動(啟動後才建立OS執行緒)。因為啟動執行緒,必須通過Thread的start方法啟動。那就來找找start方法在何處呼叫。

在ThreadPoolExecutor的addWorker中,我們找到,當建立的Worker物件成功加入workers集合後,將啟動對應執行緒。

private boolean addWorker(Runnable firstTask, boolean core) { //core表示是否是核心執行緒
    //先試圖改變控制資訊內 工作執行緒數 的值
    retry:
    for (;;) {
        //獲得控制資訊
        int c = ctl.get();
        //從控制資訊內 獲取執行緒池執行狀態
        int rs = runStateOf(c);

        //如果已經SHUTDOWN或者STOP則不再新增新工作執行緒
        //除非,在SHUTDOWN狀態下,有任務尚未完成,不接受新任務
        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;
            //CAS改變控制資訊內  工作執行緒數的值 +1 ,並結束自旋
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                continue retry;
        }
    }


    boolean workerStarted = false; //worker執行緒是否已經啟動
    boolean workerAdded = false; //worker執行緒是否已加入workers集合
    Worker w = null;
    try {
        w = new Worker(firstTask); //建立新執行緒,把初始任務賦值給它
        final Thread t = w.thread; //獲取Worker的執行緒引用
        if (t != null) {
            //因為要修改集合HashSet,故需獲取執行緒池的鎖,以保證執行緒安全
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                
                //獲取鎖後再次檢查狀態,有可能在獲得鎖之前,執行緒池已經被shutdown了
                int rs = runStateOf(ctl.get());

                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) //提前檢查執行緒能否start
                        throw new IllegalThreadStateException();
                    //把worker物件加入workers集合
                    workers.add(w);
                    int s = workers.size();
                    //更新largetstPoolSize,此欄位表示執行緒池執行時,最多開啟過多少個執行緒
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    //執行緒已加入集合,如果前面出現異常,這裡不會被執行
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            //如果新增成功,則啟動執行緒
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        //如果啟動失敗了,則表示新增Worker失敗,回滾
        if (! workerStarted)
            //這個方法,會把前面新增到workers集合中的對應worker刪除
            //並且把前面更新的 控制資訊內的工作執行緒數再減回來
            addWorkerFailed(w);
    }
    return workerStarted;
}

 

那執行緒啟動後,將執行什麼方法呢?

  那當然是執行Thread物件的run方法了,由於這裡採用的是傳遞Runnable物件的方式建立執行緒任務,故Thread的run方法執行的是其target的run方法。而這個target正是前面傳遞給它的Worker。故執行的是Worker的run方法,如下:

這裡的runWorker是其外部類ThreadPoolExecutor的方法。

final void runWorker(Worker w) {
    //獲得當前執行這段程式碼的執行緒
    Thread wt = Thread.currentThread();
    //先嚐試從worker取得初始任務
    Runnable task = w.firstTask;
    w.firstTask = null;
    //允許中斷,unlock後state=1,中斷方法獲取到鎖,則判斷為空閒執行緒,可中斷
    w.unlock(); 
    boolean completedAbruptly = true;
    try {
        //不斷地取任務執行、 其中getTask提供阻塞。如果getTask返回null則退出迴圈
        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==true
        completedAbruptly = false;
    } finally {
        //工作執行緒退出的處理操作,如獲取當前worker完成的任務量
        //如果異常退出,還需彌補,補充工作執行緒等等
        processWorkerExit(w, completedAbruptly);
    }
}

       注:這裡還提供了beforeExecute和afterExecute兩個鉤子函式,如果子類有需要,可以覆蓋它們。在這兩個時刻做一些操作。

  也就是說,每個工作者任務繫結的執行緒,執行的就是上述程式碼。那麼就會有多個執行緒訪問上述程式碼。問題來了,上述程式碼會不會出現執行緒安全問題?

  執行緒安全問題多出於多個執行緒對同一資源的訪問,但是上述程式碼中,每個執行緒操作的是各自繫結的Worker。這些執行緒唯一有交集的,就是取任務操作了。但是任務已經交由BlockingQueue處理了,BlockingQueue的同步特性使得多個執行緒能夠安全地獲取任務。也就是說,不會有執行緒安全問題。

ThreadPoolExecutor與ThreadPool線上程池的實現上有何差別

注:在之前的博文【胡思亂想】JNI與執行緒池的維護 中有引用一個執行緒池的實現案例,後文就叫他ThreadPool,該案例基本實現了執行緒池的功能。但是在實際生產中,由於有更細緻的需求,執行緒池的實現也複雜的多。JDK就有執行緒池的實現,ThreadPoolExecutor。

至此,我們來對比一下ThreadPoolExecutor與ThreadPool兩個執行緒池實現的差別

ThreadPool中,工作者執行緒完成手頭任務後,是迴歸到執行緒池,等待ThreadPool給它分配任務。(ThreadPool是一個執行緒類),也就是說在ThreadPool的實現中執行緒池還有一個執行緒用來分發任務

ThreadPoolExecutor中,工作者執行緒一旦完成手頭的任務,就自行從佇列中獲取新的任務接著做。如果沒有任務,將被阻塞,其執行緒池把任務分發(可能需要的同步,阻塞)的責任剝離了出來,交由BlockingQueue進行處理。

 

相關文章