Java線程池(ThreadPoolExecutor)原理分析與使用__java

來源:互聯網
上載者:User

在我們的開發中“池”的概念並不罕見,有資料庫連接池、線程池、對象池、常量池等等。下面我們主要針對線程池來一步一步揭開線程池的面紗。 使用線程池的好處

1、降低資源消耗
可以重複利用已建立的線程降低線程建立和銷毀造成的消耗。
2、提高響應速度
當任務到達時,任務可以不需要等到線程建立就能立即執行。
3、提高線程的可管理性
線程是稀缺資源,如果無限制地建立,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一分配、調優和監控 線程池的工作原理

首先我們看下當一個新的任務提交到線程池之後,線程池是如何處理的
1、線程池判斷核心線程池裡的線程是否都在執行任務。如果不是,則建立一個新的背景工作執行緒來執行任務。如果核心線程池裡的線程都在執行任務,則執行第二步。
2、線程池判斷工作隊列是否已經滿。如果工作隊列沒有滿,則將新提交的任務儲存在這個工作隊列裡進行等待。如果工作隊列滿了,則執行第三步
3、線程池判斷線程池的線程是否都處於工作狀態。如果沒有,則建立一個新的背景工作執行緒來執行任務。如果已經滿了,則交給飽和策略來處理這個任務 線程池飽和策略

這裡提到了線程池的飽和策略,那我們就簡單介紹下有哪些飽和策略:
AbortPolicy
為java線程池預設的阻塞策略,不執行此任務,而且直接拋出一個運行時異常,切記ThreadPoolExecutor.execute需要try catch,否則程式會直接退出。
DiscardPolicy
直接拋棄,任務不執行,空方法
DiscardOldestPolicy
從隊列裡面拋棄head的一個任務,並再次execute 此task。
CallerRunsPolicy
在調用execute的線程裡面執行此command,會阻塞入口
使用者自訂拒絕策略(最常用)
實現RejectedExecutionHandler,並自己定義策略模式
下我們以ThreadPoolExecutor為例展示下線程池的工作流程圖


1、如果當前啟動並執行線程少於corePoolSize,則建立新線程來執行任務(注意,執行這一步驟需要擷取全域鎖)。
2、如果啟動並執行線程等於或多於corePoolSize,則將任務加入BlockingQueue。
3、如果無法將任務加入BlockingQueue(隊列已滿),則在非corePool中建立新的線程來處理任務(注意,執行這一步驟需要擷取全域鎖)。
4、如果建立新線程將使當前啟動並執行線程超出maximumPoolSize,任務將被拒絕,並調用
RejectedExecutionHandler.rejectedExecution()方法。
ThreadPoolExecutor採取上述步驟的總體設計思路,是為了在執行execute()方法時,儘可能地避免擷取全域鎖(那將會是一個嚴重的可伸縮瓶頸)。在ThreadPoolExecutor完成預熱之後(當前啟動並執行線程數大於等於corePoolSize),幾乎所有的execute()方法調用都是執行步驟2,而步驟2不需要擷取全域鎖。 關鍵方法源碼分析

我們看看核心方法添加到線程池方法execute的源碼如下:
1、如果當前啟動並執行線程少於corePoolSize,則建立新線程來執行任務(注意,執行這一步驟需要擷取全域鎖)。
2、如果啟動並執行線程等於或多於corePoolSize,則將任務加入BlockingQueue。
3、如果無法將任務加入BlockingQueue(隊列已滿),則在非corePool中建立新的線程來處理任務(注意,執行這一步驟需要擷取全域鎖)。
4、如果建立新線程將使當前啟動並執行線程超出maximumPoolSize,任務將被拒絕,並調用
RejectedExecutionHandler.rejectedExecution()方法。
ThreadPoolExecutor採取上述步驟的總體設計思路,是為了在執行execute()方法時,儘可能地避免擷取全域鎖(那將會是一個嚴重的可伸縮瓶頸)。在ThreadPoolExecutor完成預熱之後(當前啟動並執行線程數大於等於corePoolSize),幾乎所有的execute()方法調用都是執行步驟2,而步驟2不需要擷取全域鎖。 關鍵方法源碼分析

我們看看核心方法添加到線程池方法execute的源碼如下:

     //     //Executes the given task sometime in the future.  The task     //may execute in a new thread or in an existing pooled thread.     //     // If the task cannot be submitted for execution, either because this     // executor has been shutdown or because its capacity has been reached,     // the task is handled by the current {@code RejectedExecutionHandler}.     //     // @param command the task to execute     // @throws RejectedExecutionException at discretion of     //         {@code RejectedExecutionHandler}, if the task     //         cannot be accepted for execution     // @throws NullPointerException if {@code command} is null     //    public void execute(Runnable command) {        if (command == null)            throw new NullPointerException();        //         // Proceed in 3 steps:         //         // 1. If fewer than corePoolSize threads are running, try to         // start a new thread with the given command as its first         // task.  The call to addWorker atomically checks runState and         // workerCount, and so prevents false alarms that would add         // threads when it shouldn't, by returning false.         // 翻譯如下:         // 判斷當前的線程數是否小於corePoolSize如果是,使用入參任務通過addWord方法建立一個新的線程,         // 如果能完成新線程建立exexute方法結束,成功提交任務         // 2. If a task can be successfully queued, then we still need         // to double-check whether we should have added a thread         // (because existing ones died since last checking) or that         // the pool shut down since entry into this method. So we         // recheck state and if necessary roll back the enqueuing if         // stopped, or start a new thread if there are none.         // 翻譯如下:         // 在第一步沒有完成任務提交;狀態為運行並且能否成功加入任務到工作隊列後,再進行一次check,如果狀態         // 在任務排入佇列後變為了非運行(有可能是在執行到這裡線程池shutdown了),非運行狀態下當然是需要         // reject;然後再判斷當前線程數是否為0(有可能這個時候線程數變為了0),如是,新增一個線程;         // 3. If we cannot queue task, then we try to add a new         // thread.  If it fails, we know we are shut down or saturated         // and so reject the task.         // 翻譯如下:         // 如果不能加入任務到工作隊列,將嘗試使用任務新增一個線程,如果失敗,則是線程池已經shutdown或者線程池         // 已經達到飽和狀態,所以reject這個他任務         //        int c = ctl.get();        // 背景工作執行緒數小於核心線程數        if (workerCountOf(c) < corePoolSize) {            // 直接啟動新線程,true表示會再次檢查workerCount是否小於corePoolSize            if (addWorker(command, true))                return;            c = ctl.get();        }        // 如果背景工作執行緒數大於等於核心線程數        // 線程的的狀態未RUNNING並且隊列notfull        if (isRunning(c) && workQueue.offer(command)) {            // 再次檢查線程的運行狀態,如果不是RUNNING直接從隊列中移除            int recheck = ctl.get();            if (! isRunning(recheck) && remove(command))                // 移除成功,拒絕該非啟動並執行任務                reject(command);            else if (workerCountOf(recheck) == 0)                // 防止了SHUTDOWN狀態下沒有活動線程了,但是隊列裡還有任務沒執行這種特殊情況。                // 添加一個null任務是因為SHUTDOWN狀態下,線程池不再接受新任務                addWorker(null, false);        }        // 如果隊列滿了或者是非啟動並執行任務都拒絕執行        else if (!addWorker(command, false))            reject(command);    }

下面我們繼續看看addWorker是如何?的:

  private boolean addWorker(Runnable firstTask, boolean core) {        // java標籤        retry:        // 死迴圈        for (;;) {            int c = ctl.get();            // 擷取當前線程狀態            int rs = runStateOf(c);            // Check if queue empty only if necessary.            // 這個邏輯判斷有點繞可以改成             // rs >= shutdown && (rs != shutdown || firstTask != null || workQueue.isEmpty())            // 邏輯判斷成立可以分為以下幾種情況均不接受新任務            // 1、rs > shutdown:--不接受新任務            // 2、rs >= shutdown && firstTask != null:--不接受新任務            // 3、rs >= shutdown && workQueue.isEmppty:--不接受新任務            // 邏輯判斷不成立            // 1、rs==shutdown&&firstTask != null:此時不接受新任務,但是仍會執行隊列中的任務            // 2、rs==shotdown&&firstTask == null:會執行addWork(null,false)            //  防止了SHUTDOWN狀態下沒有活動線程了,但是隊列裡還有任務沒執行這種特殊情況。            //  添加一個null任務是因為SHUTDOWN狀態下,線程池不再接受新任務            if (rs >= SHUTDOWN &&! (rs == SHUTDOWN && firstTask == null &&! workQueue.isEmpty()))                return false;            // 死迴圈            // 如果線程池狀態為RUNNING並且隊列中還有需要執行的任務            for (;;) {                // 擷取線程池中線程數量                int wc = workerCountOf(c);                // 如果超出容量或者最大線程池容量不在接受新任務                if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))                    return false;                // 安全執行緒增加背景工作執行緒數                if (compareAndIncrementWorkerCount(c))                    // 跳出retry                    break retry;                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 {            final ReentrantLock mainLock = this.mainLock;            w = new Worker(firstTask);            final Thread t = w.thread;            if (t != null) {                // 加鎖                mainLock.lock();                try {                    // Recheck while holding lock.                    // Back out on ThreadFactory failure or if                    // shut down before lock acquired.                    int c = ctl.get();                    int rs = runStateOf(c);                    // RUNNING狀態 || SHUTDONW狀態下清理隊列中剩餘的任務                    if (rs < SHUTDOWN ||                        (rs == SHUTDOWN && firstTask == null)) {                        // 檢查線程狀態                        if (t.isAlive()) // precheck that t is startable                            throw new IllegalThreadStateException();                        // 將新啟動的線程添加到線程池中                        workers.add(w);                        // 更新線程池線程數且不超過最大值                        int s = workers.size();                        if (s > largestPoolSize)                            largestPoolSize = s;                        workerAdded = true;                    }                } finally {                    mainLock.unlock();                }                // 啟動新添加的線程,這個線程首先執行firstTask,然後不停的從隊列中取任務執行                if (workerAdded) {                    //執行ThreadPoolExecutor的runWoker方法                    t.start();                    workerStarted = true;                }            }        } finally {            // 線程啟動失敗,則從wokers中移除w並遞減wokerCount            if (! workerStarted)                // 遞減wokerCount會觸發tryTerminate方法                addWorkerFailed(w);        }        return workerStarted;    }

addWorker之後是runWorker,第一次啟動會執行初始化傳進來的任務firstTask;然後會從workQueue中取任務執行,如果隊列為空白則等待keepAliveTime這麼長時間

 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那麼getTask中會將workerCount遞減,如果異常了這個遞減操作會在processWorkerExit中處理            while (task != null || (task = getTask()) != null) {                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);        }    }   

我們看下getTask是如何執行的

private Runnable getTask() {        boolean timedOut = false; // Did the last poll() time out?        // 死迴圈        retry: for (;;) {            // 擷取線程池狀態            int c = ctl.get();            int rs = runStateOf(c);            // Check if queue empty only if necessary.            // 1.rs > SHUTDOWN 所以rs至少等於STOP,這時不再處理隊列中的任務            // 2.rs = SHUTDOWN 所以rs>=STOP肯定不成立,這時還需要處理隊列中的任務除非隊列為空白            // 這兩種情況都會返回null讓runWoker退出while迴圈也就是當前線程結束了,所以必須要decrement            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {                // 遞減workerCount值                decrementWorkerCount();                return null;            }            // 標記從隊列中取任務時是否設定逾時時間            boolean timed; // Are workers subject to culling?            // 1.RUNING狀態            // 2.SHUTDOWN狀態,但隊列中還有任務需要執行            for (;;) {                int wc = workerCountOf(c);                // 1.core thread允許被逾時,那麼超過corePoolSize的的線程必定有逾時                // 2.allowCoreThreadTimeOut == false && wc >                // corePoolSize時,一般都是這種情況,core thread即使空閑也不會被回收,只要超過的線程才會                timed = allowCoreThreadTimeOut || wc > corePoolSize;                // 從addWorker可以看到一般wc不會大於maximumPoolSize,所以更關心後面半句的情形:                // 1. timedOut == false 第一次執行迴圈, 從隊列中取出任務不為null方法返回 或者                // poll出異常了重試                // 2.timeOut == true && timed ==                // false:看後面的代碼workerQueue.poll逾時時timeOut才為true,                // 並且timed要為false,這兩個條件相悖不可能同時成立(既然有逾時那麼timed肯定為true)                // 所以逾時不會繼續執行而是return null結束線程。                if (wc <= maximumPoolSize && !(timedOut && timed))                    break;                // workerCount遞減,結束當前thread                if (compareAndDecrementWorkerCount(c))                    return null;                c = ctl.get(); // Re-read ctl                // 需要重新檢查線程池狀態,因為上述操作過程中線程池可能被SHUTDOWN                if (runStateOf(c) != rs)                    continue retry;                // else CAS failed due to workerCount change; retry inner loop            }            try {                // 1.以指定的逾時時間從隊列中取任務                // 2.core thread沒有逾時                Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();                if (r != null)                    return r;                timedOut = true;// 逾時            } catch (InterruptedException retry) {                timedOut = false;// 線程被中斷重試            }        }    }

下面我們看下processWorkerExit是如何工作的

private void processWorkerExit(Worker w, boolean completedAbruptly) {        // 正常的話再runWorker的getTask方法workerCount已經被減一了        if (completedAbruptly)            decrementWorkerCount();        final ReentrantLock mainLock = this.mainLock;        mainLock.lock();        try {            // 累加線程的completedTasks            completedTaskCount += w.completedTasks;            // 從線程池中移除逾時或者出現異常的線程            workers.remove(w);        } finally {            mainLock.unlock();        }        // 嘗試停止線程池        tryTerminate();        int c = ctl.get();        // runState為RUNNING或SHUTDOWN        if (runStateLessThan(c, STOP)) {            // 線程不是異常結束            if (!completedAbruptly) {                // 線程池最小空閑數,允許core thread逾時就是0,否則就是corePoolSize                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;                // 如果min == 0但是隊列不為空白要保證有1個線程來執行隊列中的任務                if (min == 0 && !workQueue.isEmpty())                    min = 1;                // 線程池還不為空白那就不用擔心了                if (workerCountOf(c) >= min)                    return; // replacement not needed            }            // 1.線程異常退出            // 2.線程池為空白,但是隊列中還有任務沒執行,看addWoker方法對這種情況的處理            addWorker(null, false);        }    }

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

final void tryTerminate() {        for (;;) {            int c = ctl.get();            // 以下狀態直接返回:            // 1.線程池還處於RUNNING狀態            // 2.SHUTDOWN狀態但是任務隊列非空            // 3.runState >= TIDYING 線程池已經停止了或在停止了            if (isRunning(c) || runStateAtLeast(c, TIDYING) || (runStateOf(c) == SHUTDOWN && !workQueue.isEmpty()))                return;            // 只能是以下情形會繼續下面的邏輯:結束線程池。            // 1.SHUTDOWN狀態,這時不再接受新任務而且任務隊列也空了            // 2.STOP狀態,當調用了shutdownNow方法            // workerCount不為0則還不能停止線程池,而且這時線程都處於空閑等待的狀態            // 需要中斷讓線程“醒”過來,醒過來的線程才能繼續處理shutdown的訊號。            if (workerCountOf(c) != 0) { // Eligible to terminate                // runWoker方法中w.unlock就是為了可以被中斷,getTask方法也處理了中斷。                // ONLY_ONE:這裡只需要中斷1個線程去處理shutdown訊號就可以了。                interruptIdleWorkers(ONLY_ONE);                return;            }            final ReentrantLock mainLock = this.mainLock;            mainLock.lock();            try {                // 進入TIDYING狀態                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {                    try {                        // 子類重載:一些資源清理工作                        terminated();                    } finally {                        // TERMINATED狀態                        ctl.set(ctlOf(TERMINATED, 0));                        // 繼續awaitTermination                        termination.signalAll();                    }                    return;                }            } finally {                mainLock.unlock();            }            // else retry on failed CAS        }    }

shutdown這個方法會將runState置為SHUTDOWN,會終止所有閒置線程。shutdownNow方法將runState置為STOP。和shutdown方法的區別,這個方法會終止所有的線程。主要區別在於shutdown調用的是interruptIdleWorkers這個方法,而shutdownNow實際調用的是Worker類的interruptIfStarted方法:
他們的實現如下:

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();    }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;}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) {        }    }}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.