拜託,不要再問我線程池啦!

Java提供了幾種便捷的方法建立線程池,經過這些內置的api就可以很輕鬆的建立線程池。在java.util.concurrent包中的Executors類,其中的靜態方法就是用來建立線程池的:java

  • newFixedThreadPool():建立一個固定線程數量的線程池,並且線程池中的任務所有執行完成後,空閒的線程也不會被關閉。
  • newSingleThreadExecutor():建立一個只有一個線程的線程池,空閒時也不會被關閉。
  • newCachedThreadPool():建立一個可緩存的線程池,線程的數量爲Integer.MAX_VALUE,空閒線程會臨時緩存下來,線程會等待60s仍是沒有任務加入的話就會被關閉。
    Executors類中還有一些建立線程池的方法(jdk8新加的),可是如今這個觸及到個人知識盲區了~~

拜託,不要再問我線程池啦!
上面那幾個方法,其實都是建立了一個ThreadPoolExecutor對象做爲返回值,要搞清楚線程池的原理主要仍是要分析ThreadPoolExecutor這個類。面試

ThreadPoolExecutor的構造方法:api

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
      ...
    }

ThreadPoolExecutor的構造方法包含如下幾個參數:緩存

  • corePoolSize:核心線程數量,常駐線程池中的線程,即時線程池中沒有任務可執行,也不會被關閉。
  • maximumPoolSize:最大線程數量
  • keepAliveTime:空閒線程存活時間
  • unit:空閒線程存活時間的單位
  • workQueue:工做隊列,線程池一下忙不過來,那新來的任務就須要排隊,排除中的任務就會放在workQueue中
  • threadFactory:線程工廠,建立線程用的
  • handler:RejectedExecutionHandler實例用於在線程池中沒有空閒線程可以執行任務,而且workQueue中也容不下任務時拒絕任務時的策略。

ThreadPoolExecutor中的線程統稱爲工做線程,但有一個小概念是核心線程,核心線程由參數corePoolSize指定,如corePoolSize設置5,那線程池中就會有5條線程常駐線程池中,不會被回收掉,可是也會有例外,若是allowCoreThreadTimeOut爲true空閒一段時間後,也會被關閉。安全

線程的狀態和工做線程數量

線程中的狀態和工做線程和數量都是由ctl表示,是一個AtomicInteger類型的屬性:ide

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

ctl的高四位爲線程的狀態,其餘位數爲工做線程的數量,因此線程中最大的工做線程數量爲(2^29)-1。oop

線程池中的狀態有五種:性能

  • RUNNING:接收新的任務和處理隊列中的任務
  • SHUTDOWN:不能新增任務,可是會繼續處理已經添加的任務
  • STOP:不能新增任務,不會繼續處理已經添加任務
  • TIDYING:全部的任務已經被終止,工做線程爲0
  • TERMINATED:terminated()方法執行完成
    狀態碼的定義以下:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
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;

建立線程池

若是有面試官問:如何正確的建立線程池?千萬不要說使用Executors建立線程,雖然Executors能很方便的建立線程池,可是他提供的靜態建立方法會有一些坑。優化

主要的緣由是:maximumPoolSize和workQueue這兩個參數ui

Executors靜態方法在建立線程池時,若是maximumPoolSize設置爲Integer.MAX_VALUE,這樣會致使線程池能夠一直要以接收運行任務,可能致使cpu負載太高。

workQueue是一個阻塞隊列的實例,用於放置正在等待執行的任務。若是在建立線程種時workQueue實例沒有指定任務的容量,那麼等待隊列中能夠一直添加任務,極有可能致使oom。

因此建立線程,最好是根據線程池的用途,而後本身建立線程。

添加任務

調用線程池的execute並非當即執行任務,線程池內部用通過一頓操做,如:判斷核心線程數、是否須要添加到等待隊列中。

下來的代碼是execute的源碼,代碼很簡潔只有2個if語句:

public void execute(Runnable command) {
    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);
}
  1. 第一個if,若是當前線程池中的工做線程數量小於corePoolSize,直接建立一個工做線程執行任務
  2. 第二個if,當線程池處於運行狀態,調用workQueue.offer(command)方法將任務添加到workQueue,不然調用addWorker(command, false)嘗試去添加一個工做線程。
    整理了一張圖,把線程池分爲三部分Core Worker、Worker、workQueue:

拜託,不要再問我線程池啦!
換一種說法,在調用execute方法時,任務首先會放在Core Worker內,而後纔是workQueue,最後纔會考慮Worker。

這樣作的緣由能夠保證Core Worker中的任務執行完成後,能當即從workQueue獲取下一個任務,而不須要啓動別的工做線程,用最少的工做線程辦更多的事。

建立工做線程

在execute方法中,有三個地方調用了addWorker。addWorker方法能夠分爲二部分:

  1. 增長工做線程數量
  2. 啓動工做線程
    addWorker的方法簽名以下:
private boolean addWorker(Runnable firstTask, boolean core)
  • firstTask:第一個運行的任務,能夠爲空。若是爲空任務會從workQueue中獲取。
  • core:是不是核心工做線程

    增長工做線程數量

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

                 ....

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

上面代碼省略了一部分代碼,主要代碼都在for循環中,利用CAS鎖,安全的完成線程池狀態的檢查與增長工做線程的數量。其中的compareAndIncrementWorkerCount(c)調用就是將工做線程數量+1。

啓動工做線程

增長工做線程的數量後,緊接着就會啓動工做線程:

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

啓動工做線程的流程:

  • 建立一個Worker實例, Worker構造方法會使用ThreadFactory建立一個線程
w = new Worker(firstTask);
final Thread t = w.thread;

就不說Worker類的實現了,直接給出構造方法來細品:

Worker(Runnable firstTask) {
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);
}
  • 若是線程池狀態是在運行中,或者已經關閉,但工做線程要從workQueue中獲取任務,才能添加工做線程
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;
    }

注意::當線程池處於SHUTDOWN狀態時,它不能接收新的任務,可是能夠繼續執行未完成的任務。任務是否從workQueue中獲取,是根據firstTask判斷,每一個Worker實例都有一個firstTask屬性,若是這個值爲null,工做線程啓動的時候就會從workQueue中獲取任務,不然會執行firstTask。

  • 啓動線程
    調用線程的start方法,啓動線程。
if (workerAdded) {
    t.start();
    workerStarted = true;
}

執行任務

回過頭來看一個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接口,同時在構造方法中會將this傳遞給線程,到這裏你就知道了Worker實例中有run方法,它會在線程啓動後執行:

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

run方法內部接着調用runWorker方法運行任務,在這裏纔是真正的開始運行任務了:

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);
        }
    }
  • 獲取任務
    首先將firstTask傳遞給task臨時變量:
Runnable task = w.firstTask;

而後循環檢查task或者從workQueue中獲取任務:

while (task != null || (task = getTask()) != null) {
    ...
}

getTask()稍後再作分析。

  • 運行任務
    去掉一些狀態檢查、異常捕獲、和勾子方法調用後,保留最重要的調用task.run():
while (task != null || (task = getTask()) != null) {
     ...          
     task.run();
     ...
 }

task其實就是經過調用execute方法傳遞進來的Runnable實例,也就是你的任務。只不過它可能保存在Worker.firstTask中,或者在workQueue中,保存在哪裏在前面的任務添加順序中已經說明。

從workQueue中獲取任務

試想一下若是每一個任務執行完成,就關閉掉一個線程那有多浪費資源,這樣使用線程池也沒有多大的意義。因此線程的主要的功能就是線程複用,一旦任務執行完成直接去獲取下一個任務,或者掛起線程等待下一個提交的任務,而後等待一段時間後仍是沒有任務提交,而後才考慮是否關閉部分空閒的線程。

runWorker中會循環的獲取任務:

while (task != null || (task = getTask()) != null) {
     ...          
     task.run();
     ...
 }

上面的代碼getTask()就是從workQueue中獲取任務:

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

        for (;;) {
                    ...
             int wc = workerCountOf(c);

            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
                 ...
            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

獲取任務的時候會有兩種方式:

  1. 超時等待獲取任務
  2. 一直等待任務,直到有新任務
    若是allowCoreThreadTimeOut爲true,corePoolSize指定的核心線程數量會被忽略,直接使用 workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) 獲取任務,不然的話會根據當前工做線程的數量,若是wc > corePoolSize爲false則當前會被認爲是核心線程,調用workQueue.take()一直等待任務。

工做線程的關閉

仍是在runWorker方法中:

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) {

                  task.run();

            }

            completedAbruptly = false;   
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }
  • completedAbruptly變量:標記當前工做線程是正常執行完成,仍是異常完成的。completedAbruptly爲false能夠肯定線程池中沒有可執行的任務了。
    上面代碼是簡潔後的代碼,一個while循環保證不間斷的獲取任務,沒有任務能夠執行(task爲null)退出循環,最後再纔會調用processWorkerExit方法:
private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }

        tryTerminate();

        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            addWorker(null, false);
        }
    }

processWorkerExit接收一個Worker實例與completedAbruptly變量。processWorkerExit的大體工做流程:

  • 判斷當前工做線程是否異常完成,若是是直接減小工做線程的數量,簡單的說就是校訂一下工做線程的數量。
  • 增長完成的任務數量,將Worker從workers中移除
  • tryTerminate() 檢查線程池狀態,由於線程池能夠延遲關閉,若是你調用shutdown方法後不會當即關閉,要等待全部的任務執行完成,因此這裏調用tryTerminate()方法,嘗試去調用terminated方法。

    工做線程完成策略

若是某個工做線程完成,線程池內部會判斷是否須要從新啓動一個:

//判斷線程池狀態
if (runStateLessThan(c, STOP)) {
    if (!completedAbruptly) {
            //獲取最小工做線程數量
        int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
        //若是最小工做線程數量爲0,可是workQueue中還有任務,那重置最小工做線程數量1
        if (min == 0 && ! workQueue.isEmpty())
            min = 1;
        //若是當前工做線程數數量大於或等於最小工做線程數量,則不須要啓動新的工做線程
        if (workerCountOf(c) >= min)
            return; // replacement not needed
    }

    //啓動一個新的工做線程
    addWorker(null, false);
}

工做線程完成後有兩種處理策略:

  1. 對於異常完成的工做線程,直接啓動一個新的替換
  2. 對於正常完成的工做線程,判斷當前工做線程是否足夠,若是足夠則不須要新啓動工做線程
    注意:這裏的完成,表示工做線程的任務執行完成,workQueue中也沒有任務能夠獲取了。

線程池的關閉

關閉線程池有能夠經過shutdown方法:

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

shutdown方法,第一步就是先改變線程池的狀態,調用advanceRunState(SHUTDOWN)方法,將線程池當前狀態更改成SHUTDOWN,advanceRunState代碼以下:

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

而後當即調用interruptIdleWorkers()方法,interruptIdleWorkers()內部會調用它的重載方法interruptIdleWorkers(boolean onlyOne)同時onlyOne參數傳遞的false來關閉空閒的線程:

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

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

以上代碼會遍歷workers中的Worker實例,而後調用線程的interrupt()方法。

什麼樣的線程纔是空閒工做線程?

前面提到過在getTask()中,線程從workQueue中獲取任務時會阻塞,被阻塞的線程就是空閒的。

再次回到getTask()的代碼中:

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

        for (;;) {

              // 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;
                 ...
            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

再次分析getTask()中的代碼中有一段捕獲InterruptedException的代碼塊,interruptIdleWorkers方法中斷線程後,getTask()會捕獲中斷異常,由於外面是一個for循環,隨後代碼走到判斷線程池狀態的地方:

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

上面的代碼的會判斷當前線程池狀態,若是狀態大於STOP或者狀態等於SHUTDOWN而且workQueue爲空時則返回null,getTask()返回空那麼在runWorker中循環就會退出,當前工做線程的任務就完成了,能夠退出了:

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) {

                  task.run();

            }

            completedAbruptly = false;   
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

shutdownNow

除了shutdown方法能關閉線程池,還有shutdownNow也能夠關閉線程池。它兩的區別在於:

  • shutdownNow會清空workQueue中的任務
  • shutdownNow還會停止當前正在運行的任務
  • shutdownNow會使線程進入STOP狀態,而shutdown()是SHUTDOWN狀態
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;
    }

上面代碼基本流程:

  • advanceRunState(STOP): 使線程池進行STOP狀態,與shutdown()中的一致 ,只是使用的狀態碼是STOP
  • interruptWorkers():與shutdown()中的一致
  • drainQueue(): 清空隊列

任務是停止執行仍是繼續執行?

調用shutdownNow()後線程池處於STOP狀態,緊接着全部的工做線程都會被調用interrupt方法,若是此時runWorker還在運行會發生什麼?

在runWorker有一段代碼,就是工做線程停止的重要代碼:

final void runWorker(Worker w) {
        ...

         while (task != null || (task = getTask()) != null) {

                    if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();

                  task.run();

            }
        ...
}

重點關注:

if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();

這個if看起來有點難理解,理解下來大體意思是:若是線程池狀態大於等於STOP,當即中斷線程,不然清除線程的中斷標記,也就是說當線程池狀態爲RUNNING和SHUTDOWN時,線程的中斷標記會被清除(線程的中斷代碼在interruptWorkers方法中),能夠繼續執行任務。

以上代碼執行完成後,緊接着就會調用task.run()方法,這裏面咱們本身就能夠根據線程的中斷標記來判斷任務是否被中斷。

總結

我的水平有限,文中若有錯誤,謝謝你們指正。

本文從線程池的源碼入手,分析線程池的建立、添加任務、運行任務等流程,整個分析下來基本上大多數公司關於線程池面試的問題均可以回答得上來,固然還有一些小細節如:Worker類是繼承AQS的,爲何這麼作其實源碼中都有一些苗頭,Worker在運行時會鎖住運行的代碼塊,而shutdown在關閉空閒的Worker時,首先就要去獲取Worker的同步鎖才能繼續操做,這樣才能安全的關閉工做線程。

end

推薦閱讀:

  • TCP 三次握手、四手揮手,這樣說你能明白吧!
  • 總結三種 MySQL 大表優化方案
  • 查詢提高200倍,ClickHouse你值得擁有!
  • 爲何Redis要比Memcached更火?
  • 手把手教你使用 OpenResty 搭建高性能服務端!
    拜託,不要再問我線程池啦!若有收穫,點個在看,誠摯感謝圖片
相關文章
相關標籤/搜索