Java併發包源碼學習之線程池(一)ThreadPoolExecutor源碼分析

Java中使用線程池技術一般都是使用Executors這個工廠類,它提供了非常簡單方法來創建各種類型的線程池:

public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor() 
public static ExecutorService newCachedThreadPool() 
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

核心的接口其實是Executor,它只有一個execute方法抽象爲對任務(Runnable接口)的執行, ExecutorService接口在Executor的基礎上提供了對任務執行的生命週期的管理,主要是submitshutdown方法, AbstractExecutorServiceExecutorService一些方法做了默認的實現,主要是submit和invoke方法,而真正的任務執行 的Executor接口execute方法是由子類實現,就是ThreadPoolExecutor,它實現了基於線程池的任務執行框架,所以要了解 JDK的線程池,那麼就得先看這個類。

再看execute方法之前需要先介幾個變量或類。

 

ctl

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

這個變量是整個類的核心,AtomicInteger保證了對這個變量的操作是原子的,通過巧妙的操作,ThreadPoolExecutor用這一個變量保存了兩個內容:

  • 所有有效線程的數量
  • 各個線程的狀態(runState)

低29位存線程數,高3位存runState,這樣runState有5個值:

  • RUNNING:-536870912
  • SHUTDOWN:0
  • STOP:536870912
  • TIDYING:1073741824
  • TERMINATED:1610612736

線程池中各個狀態間的轉換比較複雜,主要記住下面內容就可以了:

  • RUNNING狀態:線程池正常運行,可以接受新的任務並處理隊列中的任務;
  • SHUTDOWN狀態:不再接受新的任務,但是會執行隊列中的任務;
  • STOP狀態:不再接受新任務,不處理隊列中的任務

圍繞ctl變量有一些操作,瞭解這些方法是看懂後面一些晦澀代碼的基礎:

 View Code

 

corePoolSize

核心線程池大小,活動線程小於corePoolSize則直接創建,大於等於則先加到workQueue中,隊列滿了才創建新的線程。

 

keepAliveTime

線程從隊列中獲取任務的超時時間,也就是說如果線程空閒超過這個時間就會終止。

 

Worker

private final class Worker extends AbstractQueuedSynchronizer implements Runnable ...

內部類Worker是對任務的封裝,所有submit的Runnable都被封裝成了Worker,它本身也是一個Runnable, 然後利用AQS框架(關於AQS可以看我這篇文章)實現了一個簡單的非重入的互斥鎖, 實現互斥鎖主要目的是爲了中斷的時候判斷線程是在空閒還是運行,可以看後面shutdownshutdownNow方法的分析。

複製代碼
// state只有0和1,互斥
protected boolean tryAcquire(int unused) {
    if (compareAndSetState(0, 1)) {
        setExclusiveOwnerThread(Thread.currentThread());
        return true;// 成功獲得鎖
    }
    // 線程進入等待隊列
    return false;
}

protected boolean tryRelease(int unused) {
    setExclusiveOwnerThread(null);
    setState(0);
    return true;
}
複製代碼

之所以不用ReentrantLock是爲了避免任務執行的代碼中修改線程池的變量,如setCorePoolSize,因爲ReentrantLock是可重入的。

 

execute

execute方法主要三個步驟:

  • 活動線程小於corePoolSize的時候創建新的線程;
  • 活動線程大於corePoolSize時都是先加入到任務隊列當中;
  • 任務隊列滿了再去啓動新的線程,如果線程數達到最大值就拒絕任務。
複製代碼
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();

    int c = ctl.get();
    // 活動線程數 < corePoolSize
    if (workerCountOf(c) < corePoolSize) {
        // 直接啓動新的線程。第二個參數true:addWorker中會重新檢查workerCount是否小於corePoolSize
        if (addWorker(command, true))
            // 添加成功返回
            return;
        c = ctl.get();
    }
    // 活動線程數 >= corePoolSize
    // runState爲RUNNING && 隊列未滿
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // double check
        // 非RUNNING狀態 則從workQueue中移除任務並拒絕
        if (!isRunning(recheck) && remove(command))
            reject(command);// 採用線程池指定的策略拒絕任務
        // 線程池處於RUNNING狀態 || 線程池處於非RUNNING狀態但是任務移除失敗
        else if (workerCountOf(recheck) == 0)
            // 這行代碼是爲了SHUTDOWN狀態下沒有活動線程了,但是隊列裏還有任務沒執行這種特殊情況。
            // 添加一個null任務是因爲SHUTDOWN狀態下,線程池不再接受新任務
            addWorker(null, false);

        // 兩種情況:
        // 1.非RUNNING狀態拒絕新的任務
        // 2.隊列滿了啓動新的線程失敗(workCount > maximumPoolSize)
    } else if (!addWorker(command, false))
        reject(command);
}
複製代碼

註釋比較清楚了就不再解釋了,其中比較難理解的應該是addWorker(null, false);這一行,這要結合addWorker一起來看。 主要目的是防止HUTDOWN狀態下沒有活動線程了,但是隊列裏還有任務沒執行這種特殊情況。

 

addWorker

這個方法理解起來比較費勁。

 View Code

 

runWorker

任務添加成功後實際執行的是runWorker這個方法,這個方法非常重要,簡單來說它做的就是:

  • 第一次啓動會執行初始化傳進來的任務firstTask;
  • 然後會從workQueue中取任務執行,如果隊列爲空則等待keepAliveTime這麼長時間。
 View Code

 

getTask

 View Code

 

processWorkerExit

線程退出會執行這個方法做一些清理工作。

 View Code

 

tryTerminate

processWorkerExit方法中會嘗試調用tryTerminate來終止線程池。這個方法在任何可能導致線程池終止的動作後執行:比如減少wokerCount或SHUTDOWN狀態下從隊列中移除任務。

 View Code

 

shutdown和shutdownNow

shutdown這個方法會將runState置爲SHUTDOWN,會終止所有空閒的線程。

複製代碼
public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            // 線程池狀態設爲SHUTDOWN,如果已經至少是這個狀態那麼則直接返回
            advanceRunState(SHUTDOWN);
            // 注意這裏是中斷所有空閒的線程:runWorker中等待的線程被中斷 → 進入processWorkerExit →
            // tryTerminate方法中會保證隊列中剩餘的任務得到執行。
            interruptIdleWorkers();
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }
複製代碼

shutdownNow方法將runState置爲STOP。和shutdown方法的區別,這個方法會終止所有的線程。

複製代碼
public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        // STOP狀態:不再接受新任務且不再執行隊列中的任務。
        advanceRunState(STOP);
        // 中斷所有線程
        interruptWorkers();
        // 返回隊列中還沒有被執行的任務。
        tasks = drainQueue();
    }
    finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}
複製代碼

主要區別在於shutdown調用的是interruptIdleWorkers這個方法,而shutdownNow實際調用的是Worker類的interruptIfStarted方法:

複製代碼
private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers) {
            Thread t = w.thread;
            // w.tryLock能獲取到鎖,說明該線程沒有在運行,因爲runWorker中執行任務會先lock,
            // 因此保證了中斷的肯定是空閒的線程。
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    }
    finally {
        mainLock.unlock();
    }
}
複製代碼
複製代碼
void interruptIfStarted() {
    Thread t;
    // 初始化時state == -1
    if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
        try {
            t.interrupt();
        } catch (SecurityException ignore) {
        }
    }
}
複製代碼

這就是前面提到的Woker類實現AQS的主要作用。

注意:shutdown方法可能會在finalize被隱式的調用。

這篇博客基本都是代碼跟註釋,所以如果不是分析ThreadPoolExecutor源碼的話看起來會非常無聊。

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