線程池狀態轉換

線程池狀態轉換流程

上一篇文章介紹了線程池的五種狀態各種的含義,今天來介紹這五種狀態是怎么流轉的,還是看線程池的源碼,首先來一張線程池狀態流轉圖


7775cb36-e5fa-4d8c-9555-9682e72aca63.png

線程池各狀態轉換

RUNING ——> SHUTDOWN:當調用線程池的 shutdown 方法時狀態變為SHUTDOWN;
RUNNING ——>STOP:當調用線程池的 shutdownNow 方法時狀態變為STOP;
SHUTDOWN ——> TIDYING:當線程池的 shutdown 方法調用時,會再調用 tryTerminated 方法,將狀態改為 TIDYING 狀態;
STOP ——> TIDYING:當線程池的 shutdownNow 方法調用時,會再調用 tryTerminated 方法,將狀態改為 TIDYING 狀態;
TIDYING——>TERMINATED:當 tryTerminated 調用時,執行完 terminated 方法后,會將線程池的狀態改為 TERMINATED 狀態;

源碼分析

RUNNING:

線程池初始的默認狀態就是 RUNNING 狀態;

 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
這個在上一篇介紹過的,它是記錄線程池狀態和線程池線程數量的一個復合類型的變量;
可以看到這個屬性初始化是,線程池的狀態就是 RUNNING 的;

SHUTDOWN:

當線程池執行完 shutdown 方法之后就會變為 SHUTDOWN 狀態;

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
         checkShutdownAccess();
         advanceRunState(SHUTDOWN);
         interruptIdleWorkers();
         onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
       tryTerminate();
}
1、checkShutdownAccess 方法:

這個方法就是使用 SecurityManager 類做了一些權限校驗,就不介紹了;

2、advanceRunState(SHUTDOWN) 方法:

這個方法從方法名可以知道,它只是預先將線程池的狀態設置為 SHUTDOWN狀態,具體看源碼;

private void advanceRunState(int targetState) {
     for (;;) {
           int c = ctl.get();
           if (runStateAtLeast(c, targetState) ||
               ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
               break;
     }
}

我們看這個方法就是通過循環重試的方式將線程池的狀態改為 SHUTDOWN;
先獲取 ctl 的值,如果線程池的狀態最少是 SHUTDOWN 則退出循環,否則通過 CAS 的方式將其狀態設置為 SHUTDOWN 狀態,設置成功后退出循環;
這里解釋一下為什么最少是 SHUTDOWN ?可以參考之前介紹線程池狀態的文章;因為線程池的狀態是遞增的,如果狀態大于或等于 SHUTDOWN 則說明線程已經調用過 shutdown 或者 shutdownNow 方法因此直接退出循環;

3、interruptIdleWorkers() 方法:

通過方法名就可以知道它是用來停止線程的方法,具體看源碼:

private void interruptIdleWorkers() {
        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();
                    }
                }
        } finally {
            mainLock.unlock();
        }
    }

因為這個方法需要操作 workers 所以需要加鎖;
具體做法也很簡單就是循環 worker 設置線程池中線程的中斷標識;
這里有個需要解釋的就是 w.tryLock() 的調用,也就是說這個方法只給能給 worker 上鎖的線程設置中斷標識(獲取鎖失敗說明線程正在執行任務,因此不會被中斷),這就是 shutdown 和shutdownNow 本質區別的地方了;
shutdown 方法執行后,它不會接受新任務了,但是它不會拋棄隊列的任務,它會繼續將任務執行完,因此他是一種溫和的停止線程池的方式;

4、onShutdown() 方法:

這個方法在源碼中是個空方法就不貼源碼了,可以看上面的注視,這個方法在 ScheduledThreadPoolExecutor 中有實現,就是做一些清理類的工作,這個方法其實是給線程池相關其他工具用戶的一個鉤子函數,主要做一些關閉線程池后的清理工作;

5、tryTerminate() 方法:

這個方法是改變線程池狀態的方法,這個方法就在后面講解吧;

STOP

當線程池在執行完 shutdownNow 方法后,就會將狀態從 RUNNING 改為 STOP 狀態;

public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP);
            interruptWorkers();
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }
1、checkShutdownAccess 方法:

這個方法就是在上面已經介紹過了,也是做權限校驗的;

2、advanceRunState(STOP) 方法:

這個方法從也介紹過,它是將線程池的狀態改成 STOP 狀態;

3、interruptWorkers() 方法:

和上面的方法類似也是用來停止線程的,只是略有不同,具體看源碼;

private void interruptWorkers() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers)
                w.interruptIfStarted();
        } finally {
            mainLock.unlock();
        }
    }

void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }

interruptWorkers 這個方法很簡單,就是循環調用 worker 里的 interruptIfStarted 方法停止線程;
我們看 interruptIfStarted 方法,這個方法會停止 state 大于等于 0 的所有線程;
而對于 worker 來說,它實現了 AQS 類,狀態 1 表示鎖被占用,0 表示鎖被釋放,因此大于等于 0 就代表了所有的線程;
因此 interruptWorkers 方法會停止所有的線程,包括正在活動中的線程;
著就是 shutdownNow 和 shutdown 方法的根本區別了;
shutdownNow方法被調用后,它除了不接受新的任務外,它會停止所有的任務,正在執行的也會被中斷,而且會拋棄隊列中的任務

4、drainQueue() 方法:

這個方法會將線程池隊列中還未執行完的任務放到另一個列表中并返回;

private List<Runnable> drainQueue() {
        BlockingQueue<Runnable> q = workQueue;
        ArrayList<Runnable> taskList = new ArrayList<Runnable>();
        q.drainTo(taskList);
        if (!q.isEmpty()) {
            for (Runnable r : q.toArray(new Runnable[0])) {
                if (q.remove(r))
                    taskList.add(r);
            }
        }
        return taskList;
    }

這個方法很簡單就是把線程池阻塞隊列中的任務都放到一個 ArrayList 的列表中,并將它返回給調用方,線程池中的阻塞隊列就會被清空了;
因此這個就是 shutdownNow 很重要的一個部分,會拋棄掉線程池隊列中所有的任務;

5、tryTerminate() 方法:

這個方法在下面在介紹,因為它和線程池的另外兩種狀態有關;

TIDYING & TERMINATED

這兩種狀態都是在調用 tryTerminate 方法之后被設置的,下面著重來介紹 tryTerminate 方法;

final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;
            if (workerCountOf(c) != 0) { // Eligible to terminate
                interruptIdleWorkers(ONLY_ONE);
                return;
            }

            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                        terminated();
                    } finally {
                        ctl.set(ctlOf(TERMINATED, 0));
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
            // else retry on failed CAS
        }
    }

a、這個方法首先會進行一些校驗的工作;
首先會校驗線程池當前的狀態,如果線程池正在運行,或者已經停止并且隊列中的任務為空就直接返回;
然后在校驗隊列中的任務,如果隊列中還有任務,會嘗試將池中非活動的線程給關閉,然后返回;
b、校驗工作完成后,才是開始真正改變線程池的狀態了;
然后會嘗試將線程池的狀態改為 TIDYING 狀態,成功后會進行下面的操作,由于這個是個 for 循環,因此使用 CAS 修改線程池狀態失敗后,會重試;
修改線程池狀態為 TIDYING 成功后,會執行 terminated 方法,這個方法其實在線程池中是一個空方法,沒有任何的實現;
其實 terminated 方法是線程池留給繼承它的其他類,來進行一些線程池關閉后的資源清理等工作的;
c、當將線程池狀態由改為 TIDYING 并且 terminated 執行完后;
當 terminated 方法執行后,一定會將線程池的狀態由 TIDYING 改為 TERMINATED;
最后會執行 termination 條件變量的 signalAll 方法,會喚醒所有被 termination 變量阻塞的線程;
關于 termination 條件變量,則需要看下面的 awaitTermination 方法了
首先來看看 termination 條件變量

private final Condition termination = mainLock.newCondition();

可以看到 termination 是線程池主鎖 mainLock 的一個條件變量,接下來看具體的方法;
public boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (;;) {
                if (runStateAtLeast(ctl.get(), TERMINATED))
                    return true;
                if (nanos <= 0)
                    return false;
                nanos = termination.awaitNanos(nanos);
            }
        } finally {
            mainLock.unlock();
        }
    }

這個方法也是一個 for 循環,其主要的部分就是會調用 termination 條件變量的 awaitNanos 方法;
這個方法會在線程池狀態是TERMINATED 時返回,也會在超時時間到達時返回;
再回到剛剛的 tryTerminated 最后的部分,也就是說當有線程調用 awaitTermination 方法后,會被阻塞掛起,但是只要線程池變成 TERMINATED 或著超時時間到達后,當前被掛起的線程才會返回;

總結

最后總結一下線程池各狀態的轉換過程,其實最重要的就是兩個關閉線程池的方法;
shutdown 方法,當調用后,線程池不會接受新的任務,但是會繼續執行隊列中的任務,也不會停止正在執行任務的線程,因此這是一種溫和的關閉線程池的方式;
shutdownNow 方法,當調用后,線程池不會接受新的任務,并且會直接拋棄隊列中的任務,還會直接停止正在執行任務的線程,因此這是一種激進的關閉線程池的方式;

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。