Java線程池核心知識詳解

 本文所說的“核心線程”、“非核心線程”是一個虛擬的概念,是爲了方便描述而虛擬出來的概念,在代碼中並沒有哪個線程被標記爲“核心線程”或“非核心線程”,所有線程都是一樣的,只是當線程池中的線程多於指定的核心線程數量時,會將多出來的線程銷燬掉,池中只保留指定個數的線程。那些被銷燬的線程是隨機的,可能是第一個創建的線程,也可能是最後一個創建的線程,或其它時候創建的線程。一開始我以爲會有一些線程被標記爲“核心線程”,而其它的則是“非核心線程”,在銷燬多餘線程的時候只銷毀那些“非核心線程”,而“核心線程”不被銷燬。這種理解是錯誤的。

  在ThreadPollExcutor類中,有一個字段 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); 是對線程池的運行狀態和線程池中有效線程的數量進行控制的, 它包含兩部分信息: 線程池的運行狀態 (runState) 和線程池內有效線程的數量 (workerCount),還有幾個對ctl進行計算的方法:

// 獲取運行狀態
private static int runStateOf(int c) { return c & ~CAPACITY; }

// 獲取活動線程數
private static int workerCountOf(int c) { return c & CAPACITY; }
以上兩個方法在源碼中經常用到,結合我們的目標,對運行狀態的一些判斷及處理可以不用去管,而對當前活動線程數要加以關注等等。

下面將遵循這些原則來分析源碼。

解惑
當我們要向線程池添加一個任務時是調用ThreadPoolExcutor對象的execute(Runnable command)方法來完成的,所以先來看看ThreadPollExcutor類中的execute(Runnable command)方法的源碼:

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);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    else if (!addWorker(command, false))
        reject(command);
}

按照我們在分析方法中提到的一些原則,去掉一些相關性不強的代碼,看看核心代碼是怎樣的。

// 爲分析而簡化後的代碼
public void execute(Runnable command) {

    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        // 如果當前活動線程數小於corePoolSize,則新建一個線程放入線程池中,並把任務添加到該線程中
        if (addWorker(command, true))
        return;
        c = ctl.get();
    }

    // 如果當前活動線程數大於等於corePoolSize,則嘗試將任務放入緩存隊列
    if (workQueue.offer(command)) {
        int recheck = ctl.get();
        if (workerCountOf(recheck) == 0)
        addWorker(null, false);
    }else {
        // 緩存已滿,新建一個線程放入線程池,並把任務添加到該線程中(此時新建的線程相當於非核心線程)
        addWorker(command, false)
    }
}

這樣一看,邏輯應該清晰很多了。

如果 當前活動線程數 < 指定的核心線程數,則創建並啓動一個線程來執行新提交的任務(此時新建的線程相當於核心線程);

如果 當前活動線程數 >= 指定的核心線程數,且緩存隊列未滿,則將任務添加到緩存隊列中;

如果 當前活動線程數 >= 指定的核心線程數,且緩存隊列已滿,則創建並啓動一個線程來執行新提交的任務(此時新建的線程相當於非核心線程);

接下來看 addWorker(Runnable firstTask, boolean core)方法

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        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;
            if (compareAndIncrementWorkerCount(c))
            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 {
        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.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                    largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

同樣,我們也來簡化一下:

// 爲分析而簡化後的代碼
private boolean addWorker(Runnable firstTask, boolean core) {

    int wc = workerCountOf(c);
    if (wc >= (core ? corePoolSize : maximumPoolSize))
    // 如果當前活動線程數 >= 指定的核心線程數,不創建核心線程
    // 如果當前活動線程數 >= 指定的最大線程數,不創建非核心線程 
    return false;

    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        // 新建一個Worker,將要執行的任務作爲參數傳進去
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            workers.add(w);
            workerAdded = true;
            if (workerAdded) {
                // 啓動剛剛新建的那個worker持有的線程,等下要看看這個線程做了啥
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

看到這裏,我們大概能猜測到,addWorker方法的功能就是新建一個線程並啓動這個線程要執行的任務應該就是在這個線程中執行。爲了證實我們的這種猜測需要再來看看Worker這個類。

private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable{
    // ....
}

Worker(Runnable firstTask) {
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);
}

從上面的Worker類的聲明可以看到,它實現了Runnable接口,以及從它的構造方法中可以知道待執行的任務賦值給了它的變量firstTask,並以它自己爲參數新建了一個線程賦值給它的變量thread,那麼運行這個線程的時候其實就是執行Worker的run()方法,來看一下這個方法:

public void run() {
    runWorker(this);
}

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        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);
    }
}

在run()方法中只調了一下 runWorker(this) 方法,再來簡化一下這個 runWorker() 方法

// 爲分析而簡化後的代碼

final void runWorker(Worker w) {
    Runnable task = w.firstTask;
    w.firstTask = null;
    while (task != null || (task = getTask()) != null) {
        try {
            task.run();
        } finally {
            task = null;
        }
    }
}

很明顯,runWorker()方法裏面執行了我們新建Worker對象時傳進去的待執行的任務,到這裏爲止貌似這個worker的run()方法就執行完了,既然執行完了那麼這個線程也就沒用了,只有等待虛擬機銷燬了。那麼回顧一下我們的目標:Java線程池中的核心線程是如何被重複利用的?好像並沒有重複利用啊,新建一個線程,執行一個任務,然後就結束了,銷燬了。沒什麼特別的啊,難道有什麼地方漏掉了,被忽略了?再仔細看一下runWorker()方法的代碼,有一個while循環,當執行完firstTask後task==null了,那麼就會執行判斷條件 (task = getTask()) != null,我們假設這個條件成立的話,那麼這個線程就不止只執行一個任務了,可以執行多個任務了,也就實現了重複利用了。答案呼之欲出了,接着看getTask()方法

private Runnable getTask() {
    lean timedOut = false; // Did the last poll() time out?
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // Are workers subject to culling?
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        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;
            }
    }
}

老規矩,簡化一下代碼來看:

// 爲分析而簡化後的代碼
private Runnable getTask() {
    boolean timedOut = false;
    for (;;) {
        int c = ctl.get();
        int wc = workerCountOf(c);

        // timed變量用於判斷是否需要進行超時控制。
        // allowCoreThreadTimeOut默認是false,也就是核心線程不允許進行超時;
        // wc > corePoolSize,表示當前線程池中的線程數量大於核心線程數量;
        // 對於超過核心線程數量的這些線程,需要進行超時控制
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        if (timed && timedOut) {
            // 如果需要進行超時控制,且上次從緩存隊列中獲取任務時發生了超時,那麼嘗試將workerCount減1,即當前活動線程數減1,
            // 如果減1成功,則返回null,這就意味着runWorker()方法中的while循環會被退出,其對應的線程就要銷燬了,也就是線程池中少了一個線程了
            if (compareAndDecrementWorkerCount(c))
            return null;
            continue;
        }

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

            // 注意workQueue中的poll()方法與take()方法的區別
            //poll方式取任務的特點是從緩存隊列中取任務,最長等待keepAliveTime的時長,取不到返回null
            //take方式取任務的特點是從緩存隊列中取任務,若隊列爲空,則進入阻塞狀態,直到能取出對象爲止

            if (r != null)
            return r;
            timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
        }
    }
}

從以上代碼可以看出,getTask()的作用是

  如果當前活動線程數大於核心線程數,當去緩存隊列中取任務的時候,如果緩存隊列中沒任務了,則等待keepAliveTime的時長,此時還沒任務就返回null,這就意味着runWorker()方法中的while循環會被退出,其對應的線程就要銷燬了,也就是線程池中少了一個線程了。因此只要線程池中的線程數大於核心線程數就會這樣一個一個地銷燬這些多餘的線程。

  如果當前活動線程數小於等於核心線程數,同樣也是去緩存隊列中取任務,但當緩存隊列中沒任務了,就會進入阻塞狀態,直到能取出任務爲止,因此這個線程是處於阻塞狀態的,並不會因爲緩存隊列中沒有任務了而被銷燬。這樣就保證了線程池有N個線程是活的,可以隨時處理任務,從而達到重複利用的目的。

小結
通過以上的分析,應該算是比較清楚地解答了“線程池中的核心線程是如何被重複利用的”這個問題,同時也對線程池的實現機制有了更進一步的理解:

  當有新任務來的時候,先看看當前的線程數有沒有超過核心線程數,如果沒超過就直接新建一個線程來執行新的任務,如果超過了就看看緩存隊列有沒有滿,沒滿就將新任務放進緩存隊列中,滿了就新建一個線程來執行新的任務,如果線程池中的線程數已經達到了指定的最大線程數了,那就根據相應的策略拒絕任務。

  當緩存隊列中的任務都執行完了的時候,線程池中的線程數如果大於核心線程數,就銷燬多出來的線程,直到線程池中的線程數等於核心線程數。此時這些線程就不會被銷燬了,它們一直處於阻塞狀態,等待新的任務到來

參考:

https://blog.csdn.net/aa1215018028/article/details/103863854

 

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