線程池原理

1、由于系統創建和銷毀線程都會占用系統資源(CPU時間),如果對于某些執行耗時很少,但是數量很多的任務,大部分的時間都會花在創建和銷毀線程,所以引入了線程池的概念;其實原理和數據庫連接池(對象池)是一樣的,都是為了避免某些不必要的損耗。

2、我們可以使用構造方法去初始化線程池,

ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 5, 10, TimeUnit.MINUTES, new LinkedBlockingQueue<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

其中有幾個必要的參數:

(1)corePoolSize: 核心線程池的大小(可以理解為空閑時期線程池中最小數目,但是需要任何時期線程數量大于等于過corePoolSize),必須大于等于0。
(2)maximumPoolSize: 線程池中最大線程數量,必須大于0。
(3)keepAliveTime: 空閑時期非核心線程最大存活時間,必須大于0。
(4)workQueue: 任務隊列。
(5)threadFactory: 線程創建工廠。
(6)handler: 飽和策略。

上面幾個參數很好理解,也是初始化線程池的必要參數;然后線程池還提供了一個比較有意思的參數:

allowCoreThreadTimeOut: 是否允許核心線程超時,它的默認值是false

從字面意思理解,就是是否讓核心線程超時銷毀,所以我們一般所理解的核心線程不會被銷毀,在這個值設置為true的時候,就是不正確的哦(我就被面試官問到過,然后還自信滿滿的說核心線程不會被銷毀!);具體使用后面解釋。

3、現在我們需要使用線程池來執行我們的任務,它提供了

// 使用Future模式,有三種重載
Future future = executor.submit(() -> System.out.println("do work"));

// 普通提交任務方式
executor.execute(() -> System.out.println("do work"));

等幾種方法提交任務。但是最終都是使用execute方法去執行任務,只不過submit是對我們的任務進行了封裝;所以我們關注execute的邏輯,究竟線程池是怎樣幫助我們完成任務的。

/*
 * 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.
 *
 * 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.
 *
 * 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.
 */
// 獲取當前運行狀態以及線程數量
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();
    // 再次檢測,如果當前運行狀態是非RUNNING,而且任務移除成功,那么拒絕任務(會執行飽和策略)
    if (! isRunning(recheck) && remove(command))
        reject(command);
    // 當前運行狀態是RUNNING或者移除任務失敗,再判斷未終止的線程數是否等于0,是則創建新線程
    else if (workerCountOf(recheck) == 0)
        addWorker(null, false);
}
// 當運行狀態為非RUNNING或者隊列滿了(邏輯上如此,但是實際在addWorker中如果運行狀態是非RUNNING并且傳入任務非空,是無法創建新線程的),創建新線程;如果創建失敗(由于運行狀態、或者最大線程數),則拒絕任務
else if (!addWorker(command, false))
    reject(command);

以上是execute的源碼,大致上的邏輯我們都清楚了,有一點需要我們注意,就是核心線程的創建,它并不是有可執行任務的核心線程就不去創建,而是只要當前線程數小于核心線程數的時候,有新任務添加就會直接創建核心線程,然后我們需要明白它是如何判斷運行狀態的;線程池中提供了幾個常量:

// 位移位數 32 - 3
private static final int COUNT_BITS = Integer.SIZE - 3;
// 計量新建線程數量的最大值 000111...111(29個1)
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// runState is stored in the high-order bits
// RUNNING狀態 111000...000(29個0)
private static final int RUNNING    = -1 << COUNT_BITS;
// SHUTDOWN狀態 000000...000(32個0)
private static final int SHUTDOWN   =  0 << COUNT_BITS;
// STOP狀態 001000...000(29個0)
private static final int STOP       =  1 << COUNT_BITS;
// TIDYING狀態 010000...000(30個0)
private static final int TIDYING    =  2 << COUNT_BITS;
// TERMINATED狀態 011000...000(29個0)
private static final int TERMINATED =  3 << COUNT_BITS;

以上常量就是用來表示線程池運行狀態的,然后記錄當前運行狀態以及線程數量用

// 將當前線程池設置為RUNNING狀態,并計入0個線程
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

所以我們也就可以理解,它用了32位的整型做狀態表示以及計數,前三位表示運行狀態,后29位用來計數。所以對于源碼里面的幾個函數我們也就可以理解了

// 判斷當前運行狀態 c是ctl.get()獲取的當前運行狀態以及線程數量值,然后與上111000...000
private static int runStateOf(int c)     { return c & ~CAPACITY; }
// 計算當前線程數目 c是ctl.get()獲取的,然后與上000111...111
private static int workerCountOf(int c)  { return c & CAPACITY; }
// 運行狀態(rs)下計入線程數量(wc)
private static int ctlOf(int rs, int wc) { return rs | wc; }
// 判斷運行狀態大小
private static boolean runStateLessThan(int c, int s) { return c < s; }
private static boolean runStateAtLeast(int c, int s) { return c >= s; }
// 判斷是否RUNNING狀態
private static boolean isRunning(int c) { return c < SHUTDOWN; }

現在關于整個execute的執行邏輯、判斷條件基本理解,所以我們需要理解它是如何添加任務,如何讓線程運行起來,執行完任務之后如何去等待繼續去執行新任務。
以下是addWorker的源碼,我們分為兩部分分析,先看CAS判斷:

retry:
for (;;) {
    int c = ctl.get();
    // 獲取當前運行狀態
    int rs = runStateOf(c);

    // Check if queue empty only if necessary.
    //這里就是我們之前說的,如果運行狀態是非RUNNING并且當前任務是非空,是無法創建新線程的
    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;
        // 比較并增加1,如果成功,那么結束判斷,進入創建線程邏輯
        if (compareAndIncrementWorkerCount(c))
            break retry;
        // 重新判斷運行狀態,如果有變化,則重新進入retry循環,否則繼續當前循環
        c = ctl.get();  // Re-read ctl
        if (runStateOf(c) != rs)
            continue retry;
        // else CAS failed due to workerCount change; retry inner loop
    }
}

以上是CAS算法判斷是否能夠新創建線程,如果成功break出retry循環,那么就進入創建線程的邏輯。
然后我們在分析線程創建:

boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
    // 創建Worker(就是我們的線程),這里Worker中會帶一個Thread對象與它(Worker)做雙向引用,后續分析Worker的工作原理
    // firstTask就是我們真正的需要執行的任務
    w = new Worker(firstTask);
    // 這就是Worker中的Thread對象
    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();
                // 將新創建的Worker加入HashSet中
                workers.add(w);
                // 記錄至今最大的workers數量
                int s = workers.size();
                if (s > largestPoolSize)
                    largestPoolSize = s;
                workerAdded = true;
            }
        } finally {
            mainLock.unlock();
        }
        // 如果創建成功,則啟動Worker中的線程,這里很重要,這也是Worker的啟動,幫助我們執行任務的關鍵,需要結合Worker初始化的源碼分析,才能更好理解
        if (workerAdded) {
            t.start();
            workerStarted = true;
        }
    }
} finally {
    // 如果創建失敗,那么做失敗處理
    if (! workerStarted)
        addWorkerFailed(w);
}
// 返回是否成功的標志
return workerStarted;

上面就是創建Worker(線程)的邏輯,比較關鍵的是Worker的初始化和啟動,現在我們繼續分析Worker的源碼,理解它是如何與Thread做綁定,然后幫助我們執行任務的:

Worker(Runnable firstTask) {
    // 這里是標志Worker狀態
    setState(-1); // inhibit interrupts until runWorker
    // 需要執行的任務
    this.firstTask = firstTask;
    // 創建Thread,并且Thread中的Runnable對象是Worker本身
    this.thread = getThreadFactory().newThread(this);
}

Worker其實也是實現了Runnable接口,從構造函數我們可以知道,在初始化Worker的時候,將本身和它的Thread對象進行雙向引用,再結合addWorker中啟動Worker中Thread的邏輯,就明白了,t.start實際上是執行了Worker中的run方法,然后我們繼續分析Worker中的run方法,它執行了runWorker方法:

final void runWorker(Worker w) {
    // 獲取當前線程,也就是Worker中的Thread
    Thread wt = Thread.currentThread();
    // 獲取Worker需要執行的任務(也就是我們實際的任務)
    Runnable task = w.firstTask;
    w.firstTask = null;
    // 改變Worker的狀態
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        // 判斷當前任務是否為null,如果是空,則去隊列獲取任務
        while (task != null || (task = getTask()) != null) {
            // 改變Worker的狀態
            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;
                // 當前Worker至今完成的所有任務總和
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        // 由于獲取任務超時終止當前Worker,這里對Worker做終止處理
        processWorkerExit(w, completedAbruptly);
    }
}

以上是Worker的工作原理,其中最主要的是getTask方法,這里就是保證它不退出一直WAITING或者TIMED_WAITING,等待任務入隊的關鍵(這里有個面試題喲,面試官問過我,當線程執行完任務之后會處于什么狀態,很多人可能認為會處于阻塞狀態,因為BlockingQueue嘛,但是是不對哦,BlockingQueue中take方法是用了LockSupport.park來使當前線程進入WAITING,而poll(timeout, timeunit)方法則是用LockSupport.parkNanos使線程進入TIMED_WAITING!這里可以了解ReentrantLock;不過我們根據線程狀態改變的條件也能推斷,這里狀態改變的情況);然后我們再來看一下getTask的源碼:

/**
 * 我們需要明白一點,當getTask方法返回null的時候,就表示當前調用Worker需要終止
 */
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.
        // 如果運行狀態為SHUTDOWN并且隊列為空或者運行狀態是STOP,那么終止Worker
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // Are workers subject to culling?
        // 這里就是我們之前說的,那個比較有意思的屬性,是否讓核心線程超時
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        // 如果線程數量大于最大數量或者已經超時 并且 線程池中有線程或者隊列為空,則嘗試結束當前Worker
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            // 判斷是否需要有超時獲取任務
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

上面就是getTask的邏輯,然后主要就是在

Runnable r = timed ?
    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
    workQueue.take();

如果timed是true,也就是當前線程數量大于核心數量或者是我們把allowCoreThreadTimeOut屬性設置為true,那么就使用poll超時獲取,否則使用take一直等待獲取任務;所以其實對于線程池,核心線程也是有可能被銷毀的!

到這里,我們基本將線程池的整個工作邏輯都串起來了,也基本明白它是如何幫助我們執行任務;但是這僅僅是主干邏輯,還有很多細節,比如它的shutdown處理、terminal處理以及Worker的狀態改變等等。所以看似簡單,但是要吃透,還需要更深入的理解。

如果有不正確的地方,請幫忙指正,謝謝!

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

推薦閱讀更多精彩內容