執行緒池原始碼探究

納蘭小依發表於2020-04-16

1.執行緒池簡介

使用執行緒池,一般會使用JDK提供的幾種封裝型別,即:newFixedThreadPoolnewSingleThreadExecutornewCachedThreadPool等,這些執行緒池的定義在Executors類中,來看看相關的原始碼:

    public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }

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

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

這些方法內部都使用了ThreadPoolExecutor的構造方法,區別只是傳入的引數不同。ThreadPoolExecutor有四個過載的構造方法,最終呼叫的是由7個引數的構造器,其原始碼如下:

    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.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

引數解釋:

  • corePoolSize:核心池大小,預設情況下,執行緒池啟動之後,並不會立即建立執行緒,而是要等到任務到來之後,才建立執行緒去執行任務(除非設定了allowCoreThreadTimeOut引數,該引數會線上程池啟動之後立馬建立核心池數量的執行緒)。隨著任務的不斷增加,現有執行緒無法滿足要求,就會不斷的建立新執行緒,直到執行緒數達到corePoolSize的值,後續新來的任務會放入阻塞佇列;
  • maximumPoolSize: 最大池大小,當任務太多,阻塞佇列滿了之後,如果執行緒數量還沒有超過該引數的值,就會繼續建立新執行緒,直到執行緒數達到該引數規定的值,後續再來的任務會使用拒絕策略進行處理;
  • keepAliveTime: 如果執行緒數超過corePoolSize的值,那麼多餘的執行緒在空閒keepAliveTime時間後會被銷燬;
  • unit: keepAliveTime引數的單位;
  • workQueue: 阻塞佇列;
  • threadFactory: 執行緒工廠,建立執行緒時需要使用到該工廠;
  • handler: 拒絕策略。

2.核心欄位

ThreadPoolExecutor的核心欄位如下:

    //ctl低29位表示執行緒的數量,高3位表示執行緒池狀態,因此當前執行緒池允許的最大執行緒數量是2^29-1
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    //固定值29
    private static final int COUNT_BITS = Integer.SIZE - 3;
    //執行緒最大容量
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits
    //執行緒池的執行時狀態,負數表示正在執行,正數表示終止情況
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

3.執行緒池狀態

執行緒池的狀態有5種,狀態之間的轉換關係如下圖:

初始情況下,執行緒池建立完畢後會處於RUNNING狀態,可以正常的接受新任務;當呼叫shutdown()時,執行緒池變成SHUTDOWN狀態,此時無法接受新任務,但是會繼續執行阻塞佇列中的任務;當呼叫shutdownNow()時,執行緒由RUNNING狀態變成STOP狀態,此時不能接受新任務,並且會中斷正在執行的任務;當執行緒池中的執行緒數減少為0時,就會轉成TIDYING狀態;在TIDYING狀態會自動呼叫terminated()使執行緒池轉為TERMINATED狀態。

  • shutdown()
    shutdown()方法的邏輯分別由5個不同的方法來實現,這裡將這些方法整理在一起,如下:
    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            //檢查security manager是否允許呼叫方執行此方法
            checkShutdownAccess();
            //將執行緒池狀態更新為SHUTDOWN
            advanceRunState(SHUTDOWN);
            //中斷空閒執行緒
            interruptIdleWorkers();
            //這是一個空實現,允許子類進行重寫
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

    private void advanceRunState(int targetState) {
        for (;;) {
            int c = ctl.get();
            //如果執行緒池已經處在targetState及之後的狀態則直接結束迴圈,否則使用CAS操作將執行緒池狀態更新為targetState
            if (runStateAtLeast(c, targetState) ||
                ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
                break;
        }
    }

    private void interruptIdleWorkers() {
        interruptIdleWorkers(false);
    }

    //onlyOne表示是否只終止一個空閒執行緒
    private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        //加可重入鎖
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                //如果執行緒沒有被中斷,則嘗試獲取鎖,獲取成功後將執行緒中斷
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        //釋放鎖
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }

    final void tryTerminate() {
        //自旋
        for (;;) {
            int c = ctl.get();
            //執行緒池還在執行,或者已經是TIDYING或TERMINATED狀態,或者阻塞佇列不為空,這幾種情況不再繼續執行
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;
            //執行緒數不為0時,終止一個空閒執行緒
            if (workerCountOf(c) != 0) { // Eligible to terminate
                interruptIdleWorkers(ONLY_ONE);
                return;
            }

            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                //將執行緒池設定為DIDYING狀態
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    //設定成功後,執行terminated()方法
                    try {
                        //這也是一個空實現,子類可以根據需要進行重寫
                        terminated();
                    } finally {
                        //將執行緒池設定為TERMINATED狀態
                        ctl.set(ctlOf(TERMINATED, 0));
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
            // else retry on failed CAS
        }
    }
  • shutdownNow()
    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            //檢查security manager是否允許呼叫方執行此方法
            checkShutdownAccess();
            //將執行緒池狀態更新為STOP
            advanceRunState(STOP);
            //與shutdown的區別是,這裡會中斷所有執行緒,而不僅僅是空閒執行緒
            interruptWorkers();
            //將任務從workQueue中移除,轉移到一個ArrayList中,此操作後,workQueue為空,已有的任務無法繼承執行
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }
    
    //中斷所有執行緒
    private void interruptWorkers() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers)
                w.interruptIfStarted();
        } finally {
            mainLock.unlock();
        }
    }

4.執行任務

執行緒池通過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();
        }
        //執行緒池處於執行狀態,並且成功將任務加入阻塞佇列時,會執行下面的程式碼
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            //如果重複檢查時,執行緒池已經不是執行狀態,則將剛新增的任務從阻塞佇列中移除,並執行拒絕策略
            if (! isRunning(recheck) && remove(command))
                reject(command);
            //如果活躍執行緒為0,則建立一個非核心執行緒,並將firstTask設定為null
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //如果新增非核心執行緒失敗,則執行拒絕策略
        else if (!addWorker(command, false))
            reject(command);
    }
    
    //獲取活躍的執行緒數
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    //獲取執行緒池執行狀態
    private static int runStateOf(int c)     { return c & ~CAPACITY; }

來看看addWorker()方法的實現:

    //core表示要建立的是否是核心執行緒,true表示建立核心執行緒,false表示建立非核心執行緒
    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            //獲取執行緒池狀態
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            //rs >= SHUTDOWN,表示執行緒池已終止
            //rs>=SHUTDOWN,說明已經呼叫了shutdown()或者shutdownNow()方法,在此條件滿足的情況下,第二項條件等同於
            //rs!=SHUTDOWN || firstTask != null || workQueue.isEmpty(),滿足這三個條件的任何一個都不會再新增新任務
            //rs!=SHUTDOWN,說明是STOP、TIDYING、TERMINATE這三種
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            //執行到這裡說明:
            //① rs<SHUTDOWN,即執行緒池是執行狀態
            //② rs=SHUTDOWN,farstTask=null, 並且阻塞佇列不為空
            for (;;) {
                int wc = workerCountOf(c);
                //有三種情況會返回false:1)執行緒數達到最大值;2)當前建立核心執行緒,但是執行緒數已經達到核心池大小;
                //3)當前建立非核心執行緒,並且執行緒數達到最大池大小
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                //如果使用CAS操作成功將ctl的值加1,則跳出最外層迴圈
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                //走到這裡說明無法使用CAS更新ctl的值,說明此時發生了多執行緒競爭,需要重新檢視執行緒池的狀態
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        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是個HashSet型別,只在重入鎖程式碼中被訪問
                        workers.add(w);
                        //更新當前活躍執行緒的最大值
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    //啟動執行緒,呼叫Worker類的run()方法
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            //成功建立新執行緒時,才會設定workerStarted=true,這裡處理沒有建立新執行緒的情況
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

addWorker()方法中用到了Worker類,這是ThreadPoolExecutor的內部類,對執行緒進行了包裝,執行緒池建立或者啟動的執行緒,實際都是Worker型別的例項,其原始碼如下(省略了無關程式碼):

    private final class Worker extends AbstractQueuedSynchronizer implements Runnable
    {

        /** Thread this worker is running in.  Null if factory fails. */
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
        /** Per-thread task counter */
        volatile long completedTasks;

        Worker(Runnable firstTask) {
            setState(-1); 
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        /** Delegates main run loop to outer runWorker  */
        public void run() {
            runWorker(this);
        }

當啟動Worker執行緒時,會呼叫runWorker()方法,每一個啟動的執行緒都會在該方法的while迴圈中不斷獲取任務去執行,該方法原始碼如下:

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            //如果能夠成功拿到任務,則執行下面的程式碼塊,如果getTask()方法返回null,當前執行緒就會執行退出邏輯
            while (task != null || (task = getTask()) != null) {
                //如果能將state欄位設定為1,表示成功拿到鎖,就接著向下執行,否則執行緒會加入等待佇列,不再繼續執行
                //注意這裡是在成功拿到新任務之後才會加鎖,結合shutdown()方法的邏輯
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                //如果執行緒池正在關閉,需要中斷當前執行緒
                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);
        }
    }

beforeExecute()afterExecute()protected型別,並且預設是空實現,很明顯是留給子類去實現鉤子邏輯。上面的程式碼使用getTask()從阻塞佇列中取任務,其實現如下:

    private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

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

            // Check if queue empty only if necessary.
            //執行緒池正在關閉,或者阻塞佇列空了,就減少執行緒數,並返回null
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // Are workers subject to culling?
            //在設定了allowCoreThreadTimeOut引數後,超過給定的時間,會將空閒的核心執行緒清理掉
            //或者執行緒數量超過了核心池數量,會在一定時間後清理掉多餘的執行緒
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            //1)執行緒數量超過最大池數量,或者超時; 2)執行緒數大於1,或者阻塞佇列為空; 這兩個條件都成立時,就將ctl值減1
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                //如果設定了超時狀態,則使用poll方法取任務,超過keepAliveTime還沒有任務到來就返回true
                //否則使用take取任務,在阻塞佇列為空時會一直等待
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                //執行緒有可能在等待新任務的到來而阻塞,但是在等待的過程中呼叫shutdownNow()關閉執行緒時,執行緒會丟擲中斷異常,在這裡被捕獲
                timedOut = false;
            }
        }
    }

現在來整理一下runWorker()方法的思路:每一個新建立的執行緒都會在runWorker()方法裡通過while迴圈不斷地從阻塞佇列中獲取任務,取到任務之後就執行任務的run()方法,取不到任務就會一直阻塞,或者等待一定的時間之後,空閒執行緒超時需要回收,就會執行processWorkerExit()方法。

5.執行緒池是如何關閉的

  • shutdown()
    在介紹shutdown()方法時有一個疑問,該方法只會中斷空閒執行緒,但是非空閒的執行緒不會被中斷,即使該執行緒被阻塞,因此該方法有可能無法關閉那些一直處在等待狀態的非空閒執行緒,這一點在使用時需要注意。在runWorker()方法中,while迴圈會在成功拿到任務後才會加鎖,因此那些由於阻塞佇列為空拿不到任務而阻塞的執行緒也會被shutdown()方法中斷
while (task != null || (task = getTask()) != null) {
    //如果能將state欄位設定為1,表示成功拿到鎖,就接著向下執行,否則執行緒會加入等待佇列,不再繼續執行
    //注意這裡是在成功拿到新任務之後才會加鎖,結合shutdown()方法的邏輯
    w.lock();
    //忽略其他程式碼
}
  • shutdownNow()
    shutdownNow()會中斷所有的存活執行緒,不論這些執行緒是否空閒,因此可能會導致任務在執行的過程中丟擲異常,這點需要注意。

不論是呼叫哪個方法來關閉執行緒池,最終執行緒的退出是要根據getTask()方法來決定。當getTask()方法返回null,即當前阻塞佇列已經沒有任務時,執行緒會退出,並且在getTask()方法的自旋程式碼會首先檢查執行緒池的狀態,如下:

    if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
        decrementWorkerCount();
        return null;
    }

在呼叫shutdownNow()方法關閉執行緒池後,rs >= STOP邏輯成立,直接返回null,而shutdown()方法會繼續執行阻塞佇列中的任務,直到workQueue.isEmpty()條件為真,getTask()返回null導致執行緒一個個結束,不論是哪種情況,最終執行緒池中的執行緒數量都會變成0。

相關文章