Java多線程(三)線程池

為什么使用線程池

????當我們在使用線程時,如果每次需要一個線程時都去創建一個線程,這樣實現起來很簡單,但是會有一個問題:當并發線程數過多時,并且每個線程都是執行一個時間很短的任務就結束時,這樣創建和銷毀線程的時間要比花在實際處理任務的時間要長的多,在一個JVM里創建太多的線程可能會導致由于系統過度消耗內存或切換過度導致系統資源不足而導致OOM問題;
????線程池為線程生命周期開銷問題和資源不足問題提供了解決方案。通過對多個任務重用線程,線程創建的開銷被分攤到了多個任務上。

使用線程池的優勢與存在的風險

優勢

  • 降低系統資源消耗,通過重用已存在的線程,降低線程創建和銷毀造成的消耗。
  • 提高系統響應速度,當有任務到達時,無需等待新線程的創建便能立即執行。
  • 通過適當地調整線程池中的線程數目,可有效控制最大并發線程數,提高系統資源的使用率,同時避免過多資源競爭,避免堵塞。
  • 更強大的功能,線程池提供了定時、定期以及可控線程數等功能的線程池,使用方便簡單。

風險

死鎖

????線程池引入了一種死鎖可能,當所有池線程都在等待已阻塞的等待隊列中另一任務的執行結果,但這一任務卻因為沒有未被占用的線程而不能運行,這種情況就導致了死鎖。如:當線程池被用來實現涉及許多交互對象的模擬,被模擬的對象可以相互發送查詢,這些查詢接下來作為排隊的任務執行,查詢對象又同步等待著響應時,會發生這種情況。

資源不足

????如果線程池太大,那么被這些線程消耗的資源可能嚴重地影響系統性能。在線程之間進行切換將會浪費時間,而且使用超出比實際需要的線程可能會引起資源匱乏問題,因為池線程正在消耗一些資源,而這些資源可能會被其它任務更有效地利用。
????線程池的大小需要根據系統運行的軟硬件環境以及應用本身的特點來決定。 一般來說, 如果代碼結構合理的話, 線程數目與 CPU數量相適合即可。 如果線程運行時可能出現阻塞現象, 可相應增加池的大小; 如有必要可采用自適應算法來動態調整線程池的大小, 以提高 CPU 的有效利用率和系統的整體性能。

并發錯誤

????線程池和其它排隊機制依靠使用 wait() 和 notify() 方法,要特別注意并發錯誤, 要從邏輯上保證程序的正確性, 注意避免死鎖現象的發生。

線程泄漏

????線程池中一個嚴重的風險是線程泄漏,當從池中除去一個線程以執行一項任務,而在任務完成后該線程卻沒有返回池時,會發生這種情況。發生線程泄漏的一種情形出現在任務拋出一個 RuntimeException 或一個 Error 時。如果線程池類沒有捕捉到它們,那么線程只會退出而線程池的大小將會永久減少一個。當這種情況發生的次數足夠多時,線程池最終為空,系統將停止,因為沒有可用的線程來處理任務。
????有些任務可能會永遠等待某些資源或來自用戶的輸入,而這些資源又不能保證一定可用或著用戶一定有輸入,諸如此類的任務會永久停止,而這些停止的任務也會引起和線程泄漏同樣的問題。如果某個線程被這樣一個任務永久地消耗著,那么它實際上就被從池除去了。對于這樣的任務,要么只給予它們自己的線程,要么讓它們等待有限的時間。

請求過載

????請求過多壓垮了服務器,這種情況是可能的。在這種情形下,我們可能不想將每個到來的請求都排隊到我們的工作隊列,因為排在隊列中等待執行的任務可能會消耗太多的系統資源并引起資源缺乏。在這種情形下決定如何做取決于你;在某些情況下,可以簡單地拋棄請求,依靠更高級別的協議稍后重試請求,也可以用一個指出服務器暫時很忙的響應來拒絕請求。

如何正確的使用線程池

  • 不要對那些同步等待其它任務結果的任務排隊。這可能會導致上面所描述的那種形式的死鎖,在那種死鎖中,所有線程都被一些任務所占用,這些任務依次等待排隊任務的結果,而這些任務又無法執行,因為沒有空閑的線程可以使用。
  • 在為任務時間可能很長的線程使用合用的線程時要小心。如果程序必須等待諸如 I/O 完成這樣的某個資源,那么請指定最長的等待時間,以及隨后是失效還是將任務重新排隊以便稍后執行。
  • 理解任務。要有效地調整線程池大小,需要理解正在排隊的任務以及它們正在做什么。它們是 CPU 限制的嗎?它們是 I/O 限制的嗎?你的答案將影響如何調整應用程序。如果有不同的任務類,這些類有著截然不同的特征,那么為不同任務類設置多個工作隊列可能會有意義,這樣可以相應地調整每個池。

線程池大小的分配

????調整線程池的大小基本上就是避免兩類錯誤:線程太少或線程太多。幸運的是,對于大多數應用程序來說,太多和太少之間的余地相當寬。
????線程池的最佳大小取決于可用處理器的數目以及工作隊列中的任務的性質。若在一個具有 N 個處理器的系統上只有一個工作隊列,其中全部是計算性質的任務,在線程池具有 N 或 N+1 個線程時一般會獲得最大的 CPU 利用率。
????對于那些可能需要等待 I/O 完成的任務,需要讓池的大小超過可用處理器的數目,因為并不是所有線程都一直在工作。通過使用概要分析,可以估計某個典型請求的等待時間(WT)與服務時間(ST)之間的比例。如果我們將這一比例稱之為 WT/ST,那么對于一個具有 N 個處理器的系統,需要設置大約 N*(1+WT/ST) 個線程來保持處理器得到充分利用。
????處理器利用率不是調整線程池大小過程中的唯一考慮事項。隨著線程池的增長,你可能會碰到調度程序、可用內存方面的限制,或者其它系統資源方面的限制,例如套接字、打開的文件句柄或數據庫連接等的數目。

ThreadPoolExecutor詳解

幾個重要字段和方法

(這塊內容引用了ideabuffer
博主的http://www.lxweimin.com/p/d2729853c4da文章,并做了稍微修改,有興趣的同學可以查看這篇文章,寫的很詳細)

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;

// Packing and unpacking ctl
private static int runStateOf(int c)     { return c & ~CAPACITY; }
private static int workerCountOf(int c)  { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
  • ctl:對線程池的運行狀態和線程池中有效線程的數量進行控制的一個字段, 它包含兩部分的信息: 線程池的運行狀態 (runState) 和線程池內有效線程的數量 (workerCount),這里可以看到,使用了Integer類型來保存,高3位保存runState,低29位保存workerCount。COUNT_BITS 就是29,CAPACITY就是1左移29位減1,這個常量表示workerCount的上限值,大約是5億。
  • RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED表示線程池的五種狀態,在下面的章節中我們會具體介紹。
  • runStateOf,workerCountOf,ctlOf是對ctl進行計算的方法;
    • runStateOf:獲取運行狀態;
    • workerCountOf:獲取活動線程數;
    • ctlOf:獲取運行狀態和活動線程數的值;
  • 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.
       *
       * 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.
       */
      /*
       * 獲取線程池狀態以及有效線程數即runState以及workerCount
       */
      int c = ctl.get();
      /*
       * 如果當前線程活動數小于corePoolSize,則新建一個線程放入線程池中,并作為線程池的第一個任務
       */
      if (workerCountOf(c) < corePoolSize) {
          /*
           * 第二個參數表示限制添加線程的數量是根據corePoolSize來判斷還是maximumPoolSize來判斷;
           * 如果為true,根據corePoolSize來判斷;
           * 如果為false,則根據maximumPoolSize來判斷
           */
          if (addWorker(command, true))
              return;
          /*
           * 如果添加失敗,則重新獲取ctl值
           */
          c = ctl.get();
      }
      /*
       * 如果當前線程是運行狀態,并且添加任務到隊列中成功
       */
      if (isRunning(c) && workQueue.offer(command)) {
          /*
           * 重新獲取ctl值
           */
          int recheck = ctl.get();
          /*
           * 再次判斷線程池的運行狀態,如果不是運行狀態,由于之前已經把command添加到workQueue中了,這時需要移除該command,執行過后通過handler使用拒絕策略對該任務進行處理,整個方法返回
           */
          if (! isRunning(recheck) && remove(command))
              reject(command);
          /*
           * 獲取線程池中的有效線程數,如果數量是0,則執行addWorker方法
           * 這里傳入的參數表示:第一個參數為null,表示在線程池中創建一個線程,但不去啟動;第二個參數為false,將線程池的有限線程數量的上限設置為maximumPoolSize,添加線程時根據maximumPoolSize來判斷;
           * 如果判斷workerCount大于0,則直接返回,在workQueue中新增的command會在將來的某個時刻被執行。
           */
          else if (workerCountOf(recheck) == 0)
              addWorker(null, false);
      }
      /*
       * 如果執行到這里,有兩種情況:
       * 1. 線程池已經不是RUNNING狀態;
       * 2. 線程池是RUNNING狀態,但workerCount >= corePoolSize并且workQueue已滿。
       * 這時,再次調用addWorker方法,但第二個參數傳入為false,將線程池的有限線程數量的上限設置為maximumPoolSize;
       * 如果失敗則拒絕該任務
       */
      else if (!addWorker(command, false))
          reject(command);
}
  • addWorker方法:在線程池中創建一個新的線程并執行,firstTask參數用于指定新增的線程執行的第一個任務,core參數為true表示在新增線程時會判斷當前活動線程數是否少于corePoolSize,false表示新增線程前需要判斷當前活動線程數是否少于maximumPoolSize
/**
 * Checks if a new worker can be added with respect to current
 * pool state and the given bound (either core or maximum). If so,
 * the worker count is adjusted accordingly, and, if possible, a
 * new worker is created and started, running firstTask as its
 * first task. This method returns false if the pool is stopped or
 * eligible to shut down. It also returns false if the thread
 * factory fails to create a thread when asked.  If the thread
 * creation fails, either due to the thread factory returning
 * null, or due to an exception (typically OutOfMemoryError in
 * Thread.start()), we roll back cleanly.
 *
 * @param firstTask the task the new thread should run first (or
 * null if none). Workers are created with an initial first task
 * (in method execute()) to bypass queuing when there are fewer
 * than corePoolSize threads (in which case we always start one),
 * or when the queue is full (in which case we must bypass queue).
 * Initially idle threads are usually created via
 * prestartCoreThread or to replace other dying workers.
 *
 * @param core if true use corePoolSize as bound, else
 * maximumPoolSize. (A boolean indicator is used here rather than a
 * value to ensure reads of fresh values after checking other pool
 * state).
 * @return true if successful
 */
private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        /*
         * 獲取ctl值
         */
        int c = ctl.get();
        /*
         * 獲取線程池運行狀態
         */
        int rs = runStateOf(c);
        /*
         * 如果rs >= SHUTDOWN,即線程池處于SHUTDOWN,STOP,TIDYING,TERMINATED中的一個狀態,此時線程池不再接收新任務;
         * 接著判斷以下3個條件,只要有1個不滿足,則返回false:
         * 1. rs == SHUTDOWN,這時表示關閉狀態,不再接受新提交的任務,但卻可以繼續處理阻塞隊列中已保存的任務
         * 2. firsTask為空
         * 3. 阻塞隊列不為空
         * 
         * 首先考慮rs == SHUTDOWN的情況
         * 這種情況下不會接受新提交的任務,所以在firstTask不為空的時候會返回false;
         * 然后,如果firstTask為空,并且workQueue也為空,則返回false,
         * 因為隊列中已經沒有任務了,不需要再添加線程了
         */
        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
               return false;

        for (;;) {
            /*
             * 獲取線程數
             */
            int wc = workerCountOf(c);
            /*
             * 如果wc超過或等于CAPACITY,或者wc超過或等于corePoolSize當core為true時或wc超過或等于maximumPoolSize當core為false時,返回false
             */
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            /*
             * 嘗試增加workerCount,如果成功,則跳出第一個for循環
             */
            if (compareAndIncrementWorkerCount(c))
                    break retry;
            /*
             * 如果增加workerCount失敗,則重新獲取ctl的值
             */
            c = ctl.get();  // Re-read ctl
            /*
             * 如果當前的運行狀態不等于rs,說明狀態已被改變,返回第一個for循環繼續執行
             */
            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 {
        /*
         * 根據firstTask來創建Worker對象
         */
        w = new Worker(firstTask);
        /*
         * 每一個Worker對象都會創建一個線程
         */
        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());
                /*
                 * 如果rs是RUNNING狀態或者rs是SHUTDOWN狀態并且firstTask為null,向線程池中添加線程。因為在SHUTDOWN時不會在添加新的任務,但還是會執行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();
                    /*
                     * 更新線程池中出現的最大線程數largestPoolSize
                     */
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}
  • Worker類:線程池中的每一個線程被封裝成一個Worker對象,ThreadPool維護的其實就是一組Worker對象
/**
 * Class Worker mainly maintains interrupt control state for
 * threads running tasks, along with other minor bookkeeping.
 * This class opportunistically extends AbstractQueuedSynchronizer
 * to simplify acquiring and releasing a lock surrounding each
 * task execution.  This protects against interrupts that are
 * intended to wake up a worker thread waiting for a task from
 * instead interrupting a task being run.  We implement a simple
 * non-reentrant mutual exclusion lock rather than use
 * ReentrantLock because we do not want worker tasks to be able to
 * reacquire the lock when they invoke pool control methods like
 * setCorePoolSize.  Additionally, to suppress interrupts until
 * the thread actually starts running tasks, we initialize lock
 * state to a negative value, and clear it upon start (in
 * runWorker).
 */
private final class Worker
    extends AbstractQueuedSynchronizer
    implements Runnable
{
    /**
     * This class will never be serialized, but we provide a
     * serialVersionUID to suppress a javac warning.
     */
    private static final long serialVersionUID = 6138294804551838833L;

    /** Thread this worker is running in.  Null if factory fails. */
    final Thread thread;
    /** Initial task to run.  Possibly null. */
    Runnable firstTask;
    /** Per-thread task counter */
    volatile long completedTasks;

    /**
     * Creates with given first task and thread from ThreadFactory.
     * @param firstTask the first task (null if none)
     */
    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }

    /** Delegates main run loop to outer runWorker  */
    public void run() {
        runWorker(this);
    }

    // Lock methods
    //
    // The value 0 represents the unlocked state.
    // The value 1 represents the locked state.

    protected boolean isHeldExclusively() {
        return getState() != 0;
    }

    protected boolean tryAcquire(int unused) {
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }

    protected boolean tryRelease(int unused) {
        setExclusiveOwnerThread(null);
        setState(0);
        return true;
    }

    public void lock()        { acquire(1); }
    public boolean tryLock()  { return tryAcquire(1); }
    public void unlock()      { release(1); }
    public boolean isLocked() { return isHeldExclusively(); }

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

????Worker類繼承了AbstractQueuedSynchronizer,并實現了Runnable接口,其中firstTask用來保存傳入的任務;thread是用來處理任務的線程,是通過ThreadFactory來創建的。newThread方法傳入的參數是this,因為Worker本身繼承了Runnable接口,也就是一個線程,所以一個Worker對象在啟動的時候會調用Worker類中的run方法。
????Worker繼承了AbstractQueuedSynchronizer,使用AbstractQueuedSynchronizer來實現獨占鎖的功能。為什么不使用ReentrantLock來實現呢?可以看到tryAcquire方法,它是不允許重入的,而ReentrantLock是允許重入的:

  1. lock方法一旦獲取了獨占鎖,表示當前線程正在執行任務中;
  2. 如果正在執行任務,則不應該中斷線程;
  3. 如果該線程現在不是獨占鎖的狀態,也就是空閑的狀態,說明它沒有在處理任務,這時可以對該線程進行中斷;
  4. 線程池在執行shutdown方法或tryTerminate方法時會調用interruptIdleWorkers方法來中斷空閑的線程,interruptIdleWorkers方法會使用tryLock方法來判斷線程池中的線程是否是空閑狀態;
  5. 之所以設置為不可重入,是因為我們不希望任務在調用像setCorePoolSize這樣的線程池控制方法時重新獲取鎖。如果使用ReentrantLock,它是可重入的,這樣如果在任務中調用了如setCorePoolSize這類線程池控制的方法,會中斷正在運行的線程。

????所以,Worker繼承自AbstractQueuedSynchronizer,用于判斷線程是否空閑以及是否可以被中斷。
????此外,在構造方法中執行了setState(-1);,把state變量設置為-1,為什么這么做呢?是因為AbstractQueuedSynchronizer中默認的state是0,如果剛創建了一個Worker對象,還沒有執行任務時,這時就不應該被中斷,看一下tryAquire方法:

protected boolean tryAcquire(int unused) {
       if (compareAndSetState(0, 1)) {
           setExclusiveOwnerThread(Thread.currentThread());
           return true;
       }
       return false;
}

????tryAcquire方法是根據state是否是0來判斷的,所以,setState(-1);將state設置為-1是為了禁止在執行任務前對線程進行中斷。
????正因為如此,在runWorker方法中會先調用Worker對象的unlock方法將state設置為0.

  • runWorker 方法:Worker類中的run方法調用了runWorker方法來執行任務
/**
 * Main worker run loop.  Repeatedly gets tasks from queue and
 * executes them, while coping with a number of issues:
 *
 * 1. We may start out with an initial task, in which case we
 * don't need to get the first one. Otherwise, as long as pool is
 * running, we get tasks from getTask. If it returns null then the
 * worker exits due to changed pool state or configuration
 * parameters.  Other exits result from exception throws in
 * external code, in which case completedAbruptly holds, which
 * usually leads processWorkerExit to replace this thread.
 *
 * 2. Before running any task, the lock is acquired to prevent
 * other pool interrupts while the task is executing, and then we
 * ensure that unless pool is stopping, this thread does not have
 * its interrupt set.
 *
 * 3. Each task run is preceded by a call to beforeExecute, which
 * might throw an exception, in which case we cause thread to die
 * (breaking loop with completedAbruptly true) without processing
 * the task.
 *
 * 4. Assuming beforeExecute completes normally, we run the task,
 * gathering any of its thrown exceptions to send to afterExecute.
 * We separately handle RuntimeException, Error (both of which the
 * specs guarantee that we trap) and arbitrary Throwables.
 * Because we cannot rethrow Throwables within Runnable.run, we
 * wrap them within Errors on the way out (to the thread's
 * UncaughtExceptionHandler).  Any thrown exception also
 * conservatively causes thread to die.
 *
 * 5. After task.run completes, we call afterExecute, which may
 * also throw an exception, which will also cause thread to
 * die. According to JLS Sec 14.20, this exception is the one that
 * will be in effect even if task.run throws.
 *
 * The net effect of the exception mechanics is that afterExecute
 * and the thread's UncaughtExceptionHandler have as accurate
 * information as we can provide about any problems encountered by
 * user code.
 *
 * @param w the worker
 */
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語句期間可能也執行了shutdownNow方法,shutdownNow方法會把狀態設置為STOP
             */
            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);
    }
}

????STOP狀態要中斷線程池中的所有線程,而這里使用Thread.interrupted()來判斷是否中斷是為了確保在RUNNING或者SHUTDOWN狀態時線程是非中斷狀態的,因為Thread.interrupted()方法會復位中斷的狀態。

runWorker的執行過程:

  1. while循環不斷地通過getTask()方法獲取任務;
  2. getTask()方法從阻塞隊列中取任務;
  3. 如果線程池正在停止,那么要保證當前線程是中斷狀態,否則要保證當前線程不是中斷狀態;
  4. 調用task.run()執行任務;
  5. 如果task為null則跳出循環,執行processWorkerExit()方法;
  6. runWorker方法執行完畢,也代表著Worker中的run方法執行完畢,銷毀線程。
  • getTask方法:用來從阻塞隊列中取任務
/**
 * Performs blocking or timed wait for a task, depending on
 * current configuration settings, or returns null if this worker
 * must exit because of any of:
 * 1. There are more than maximumPoolSize workers (due to
 *    a call to setMaximumPoolSize).
 * 2. The pool is stopped.
 * 3. The pool is shutdown and the queue is empty.
 * 4. This worker timed out waiting for a task, and timed-out
 *    workers are subject to termination (that is,
 *    {@code allowCoreThreadTimeOut || workerCount > corePoolSize})
 *    both before and after the timed wait, and if the queue is
 *    non-empty, this worker is not the last thread in the pool.
 *
 * @return task, or null if the worker must exit, in which case
 *         workerCount is decremented
 */
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.
       /*
        * 如果線程池狀態rs >= SHUTDOWN,也就是非RUNNING狀態,再進行以下判斷:
       * 1. rs >= STOP,線程池是否正在stop;
       * 2. 阻塞隊列是否為空。
       * 如果以上條件滿足,則將workerCount減1并返回null。
       * 因為如果當前線程池狀態的值是SHUTDOWN或以上時,不允許再向阻塞隊列中添加任務。
       */
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // Are workers subject to culling?
        /*
         * timed變量用于判斷是否需要進行超時控制。
         * allowCoreThreadTimeOut默認是false,也就是核心線程不允許進行超時;
         * wc > corePoolSize,表示當前線程池中的線程數量大于核心線程數量;
         * 對于超過核心線程數量的這些線程,需要進行超時控制
         */
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        /*
         * wc > maximumPoolSize的情況是因為可能在此方法執行階段同時執行了setMaximumPoolSize方法;
         * timed && timedOut 如果為true,表示當前操作需要進行超時控制,并且上次從阻塞隊列中獲取任務發生了超時
         * 接下來判斷,如果有效線程數量大于1,或者阻塞隊列是空的,那么嘗試將workerCount減1;
         * 如果減1失敗,則返回重試。
         * 如果wc == 1時,也就說明當前線程是線程池中唯一的一個線程了。
         */
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            /*
             * 根據timed來判斷,如果為true,則通過阻塞隊列的poll方法進行超時控制,如果在keepAliveTime時間內沒有獲取到任務,則返回null;
             * 否則通過take方法,如果這時隊列為空,則take方法會阻塞直到隊列不為空。
             */
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            /*
             * 如果 r == null,說明已經超時,timedOut設置為true
             */
            timedOut = true;
        } catch (InterruptedException retry) {
            /*
             * 如果獲取任務時當前線程發生了中斷,則設置timedOut為false并返回循環重試
             */
            timedOut = false;
        }
    }
}

????這里重要的地方是第二個if判斷,目的是控制線程池的有效線程數量。由上文中的分析可以知道,在執行execute方法時,如果當前線程池的線程數量超過了corePoolSize且小于maximumPoolSize,并且workQueue已滿時,則可以增加工作線程,但這時如果超時沒有獲取到任務,也就是timedOut為true的情況,說明workQueue已經為空了,也就說明了當前線程池中不需要那么多線程來執行任務了,可以把多于corePoolSize數量的線程銷毀掉,保持線程數量在corePoolSize即可。
????什么時候會銷毀?當然是runWorker方法執行完之后,也就是Worker中的run方法執行完,由JVM自動回收。
????getTask方法返回null時,在runWorker方法中會跳出while循環,然后會執行processWorkerExit方法。

  • processWorkerExit 方法:
/**
 * Performs cleanup and bookkeeping for a dying worker. Called
 * only from worker threads. Unless completedAbruptly is set,
 * assumes that workerCount has already been adjusted to account
 * for exit.  This method removes thread from worker set, and
 * possibly terminates the pool or replaces the worker if either
 * it exited due to user task exception or if fewer than
 * corePoolSize workers are running or queue is non-empty but
 * there are no workers.
 *
 * @param w the worker
 * @param completedAbruptly if the worker died due to user exception
 */
private void processWorkerExit(Worker w, boolean completedAbruptly) {
    /*
     * 如果completedAbruptly值為true,則說明線程執行時出現了異常,需要將workerCount減1;
     * 如果線程執行時沒有出現異常,說明在getTask()方法中已經已經對workerCount進行了減1操作,這里就不必再減了。
     */
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        /*
         * 統計完成的任務數
         */
        completedTaskCount += w.completedTasks;
        /*
         * 從workers中移除,也就表示著從線程池中移除了一個工作線程
         */
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }
    /*
     * 根據線程池狀態進行判斷是否結束線程池
     */
    tryTerminate();

    int c = ctl.get();
    /*
     * 當線程池是RUNNING或SHUTDOWN狀態時,如果worker是異常結束,那么會直接addWorker;
     * 如果allowCoreThreadTimeOut=true,并且等待隊列有任務,至少保留一個worker;
     * 如果allowCoreThreadTimeOut=false,workerCount不少于corePoolSize。
     */
    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執行完之后,工作線程被銷毀,以上就是整個工作線程的生命周期,從execute方法開始,Worker使用ThreadFactory創建新的工作線程,runWorker通過getTask獲取任務,然后執行任務,如果getTask返回null,進入processWorkerExit方法,整個線程結束,如圖所示:


image.png
  • tryTerminate方法:根據線程池狀態進行判斷是否結束線程池
/**
 * Transitions to TERMINATED state if either (SHUTDOWN and pool
 * and queue empty) or (STOP and pool empty).  If otherwise
 * eligible to terminate but workerCount is nonzero, interrupts an
 * idle worker to ensure that shutdown signals propagate. This
 * method must be called following any action that might make
 * termination possible -- reducing worker count or removing tasks
 * from the queue during shutdown. The method is non-private to
 * allow access from ScheduledThreadPoolExecutor.
 */
final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        /*
         * 當前線程池的狀態為以下幾種情況時,直接返回:
         * 1. RUNNING,因為還在運行中,不能停止;
         * 2. TIDYING或TERMINATED,因為線程池中已經沒有正在運行的線程了;
         * 3. SHUTDOWN并且等待隊列非空,這時要執行完workQueue中的task;
         */
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
        /*
         * 如果線程數量不為0,則中斷一個空閑的工作線程,并返回
         */
        if (workerCountOf(c) != 0) { // Eligible to terminate
            interruptIdleWorkers(ONLY_ONE);
            return;
        }

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            /*
             * 這里嘗試設置狀態為TIDYING,如果設置成功,則調用terminated方法
             */
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    /*
                     * terminated方法默認什么都不做,留給子類實現
                     */
                    terminated();
                } finally {
                    /*
                     * 設置狀態為TERMINATED
                     */
                    ctl.set(ctlOf(TERMINATED, 0));
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}
  • shutdown 方法:將線程池切換到SHUTDOWN狀態,并調用interruptIdleWorkers方法請求中斷所有空閑的worker,最后調用tryTerminate嘗試結束線程池
/**
 * Initiates an orderly shutdown in which previously submitted
 * tasks are executed, but no new tasks will be accepted.
 * Invocation has no additional effect if already shut down.
 *
 * <p>This method does not wait for previously submitted tasks to
 * complete execution.  Use {@link #awaitTermination awaitTermination}
 * to do that.
 *
 * @throws SecurityException {@inheritDoc}
 */
public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        /*
         * 安全策略判斷
         */
        checkShutdownAccess();
        /*
         * 切換狀態為SHUTDOWN
         */
        advanceRunState(SHUTDOWN);
        /* 
         * 中斷空閑線程
         */
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    /*
     * 嘗試結束線程池
     */
    tryTerminate();
}

????這里思考一個問題:在runWorker方法中,執行任務時對Worker對象w進行了lock操作,為什么要在執行任務的時候對每個工作線程都加鎖呢?

  • 在getTask方法中,如果這時線程池的狀態是SHUTDOWN并且workQueue為空,那么就應該返回null來結束這個工作線程,而使線程池進入SHUTDOWN狀態需要調用shutdown方法;
  • shutdown方法會調用interruptIdleWorkers來中斷空閑的線程,interruptIdleWorkers持有mainLock,會遍歷workers來逐個判斷工作線程是否空閑。但getTask方法中沒有mainLock;
  • 在getTask中,如果判斷當前線程池狀態是RUNNING,并且阻塞隊列為空,那么會調用workQueue.take()進行阻塞;
  • 如果在判斷當前線程池狀態是RUNNING后,這時調用了shutdown方法把狀態改為了SHUTDOWN,這時如果不進行中斷,那么當前的工作線程在調用了workQueue.take()后會一直阻塞而不會被銷毀,因為在SHUTDOWN狀態下不允許再有新的任務添加到workQueue中,這樣一來線程池永遠都關閉不了了;
  • 由上可知,shutdown方法與getTask方法(從隊列中獲取任務時)存在競態條件;
  • 解決這一問題就需要用到線程的中斷,也就是為什么要用interruptIdleWorkers方法。在調用workQueue.take()時,如果發現當前線程在執行之前或者執行期間是中斷狀態,則會拋出InterruptedException,解除阻塞的狀態;
  • 但是要中斷工作線程,還要判斷工作線程是否是空閑的,如果工作線程正在處理任務,就不應該發生中斷;
  • 所以Worker繼承自AbstractQueuedSynchronizer,在工作線程處理任務時會進行lock,interruptIdleWorkers在進行中斷時會使用tryLock來判斷該工作線程是否正在處理任務,如果tryLock返回true,說明該工作線程當前未執行任務,這時才可以被中斷。
  • interruptIdleWorkers方法:遍歷workers中所有的工作線程,若線程沒有被中斷tryLock成功,就中斷該線程。
/**
 * Interrupts threads that might be waiting for tasks (as
 * indicated by not being locked) so they can check for
 * termination or configuration changes. Ignores
 * SecurityExceptions (in which case some threads may remain
 * uninterrupted).
 *
 * @param onlyOne If true, interrupt at most one worker. This is
 * called only from tryTerminate when termination is otherwise
 * enabled but there are still other workers.  In this case, at
 * most one waiting worker is interrupted to propagate shutdown
 * signals in case all threads are currently waiting.
 * Interrupting any arbitrary thread ensures that newly arriving
 * workers since shutdown began will also eventually exit.
 * To guarantee eventual termination, it suffices to always
 * interrupt only one idle worker, but shutdown() interrupts all
 * idle workers so that redundant workers exit promptly, not
 * waiting for a straggler task to finish.
 */
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();
    }
}

????為什么需要持有mainLock?因為workers是HashSet類型的,不能保證線程安全。

  • shutdownNow 方法:
/**
 * Attempts to stop all actively executing tasks, halts the
 * processing of waiting tasks, and returns a list of the tasks
 * that were awaiting execution. These tasks are drained (removed)
 * from the task queue upon return from this method.
 *
 * <p>This method does not wait for actively executing tasks to
 * terminate.  Use {@link #awaitTermination awaitTermination} to
 * do that.
 *
 * <p>There are no guarantees beyond best-effort attempts to stop
 * processing actively executing tasks.  This implementation
 * cancels tasks via {@link Thread#interrupt}, so any task that
 * fails to respond to interrupts may never terminate.
 *
 * @throws SecurityException {@inheritDoc}
 */
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;
}

shutdownNow方法與shutdown方法類似,不同的地方在于:

  1. 設置狀態為STOP;
  2. 中斷所有工作線程,無論是否是空閑的;
  3. 取出阻塞隊列中沒有被執行的任務并返回。

線程池的監控

  • getTaskCount:線程池已經執行的和未執行的任務總數;
  • getCompletedTaskCount:線程池已完成的任務數量,該值小于等于taskCount;
  • getLargestPoolSize:線程池曾經創建過的最大線程數量。通過這個數據可以知道線程池是否滿過,也就是達到了maximumPoolSize;
  • getPoolSize:線程池當前的線程數量;
  • getActiveCount:當前線程池中正在執行任務的線程數量。
    ????通過這些方法,可以對線程池進行監控,在ThreadPoolExecutor類中提供了幾個空方法,如beforeExecute方法,afterExecute方法和terminated方法,可以擴展這些方法在執行前或執行后增加一些新的操作,例如統計線程池的執行任務的時間等,可以繼承自ThreadPoolExecutor來進行擴展。

ThreadPoolExecutor的構造函數

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), handler);
}
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
}

參數解釋

  • corePoolSize : 核心池大小,即線程的數量。在創建了線程池后,默認情況下,線程池中并沒有任何線程,而是等待有任務到來才創建線程去執行任務,除非調用了prestartAllCoreThreads()或者prestartCoreThread()方法,從這2個方法的名字就可以看出,是預創建線程的意思,即在沒有任務到來之前就創建corePoolSize個線程或者一個線程。默認情況下,在創建了線程池后,線程池中的線程數為0,當有任務來之后,就會創建一個線程去執行任務,當線程池中的線程數目達到corePoolSize后,就會把到達的任務放到緩存隊列當中;
  • maximumPoolSize : 線程池最大線程數,表示在線程池中最多能創建多少個線程;
  • keepAliveTime :表示線程沒有任務執行時最多保持多久時間會終止。默認情況下,只有當線程池中的線程數大于corePoolSize時,keepAliveTime才會起作用,直到線程池中的線程數不大于corePoolSize:即當線程池中的線程數大于corePoolSize時,如果一個線程空閑的時間達到keepAliveTime,則會終止,直到線程池中的線程數不超過corePoolSize;但是如果調用了allowCoreThreadTimeOut(boolean)方法,在線程池中的線程數不大于corePoolSize時,keepAliveTime參數也會起作用,直到線程池中的線程數為0;
  • unit:參數keepAliveTime的時間單位,有7種取值,在TimeUnit類中有7種靜態屬性:
    TimeUnit.DAYS; //天
    TimeUnit.HOURS; //小時
    TimeUnit.MINUTES; //分鐘
    TimeUnit.SECONDS; //秒
    TimeUnit.MILLISECONDS; //毫秒
    TimeUnit.MICROSECONDS; //微妙
    TimeUnit.NANOSECONDS; //納秒
  • workQueue:線程池采用的緩沖隊列,用來存儲等待執行的任務,這個參數的選擇會對線程池的運行過程產生重大影響,一般來說,有以下幾種選擇:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue。
  • threadFactory:線程工廠,主要用來創建線程。默認使用Executors.defaultThreadFactory() 來創建線程。使用默認的ThreadFactory來創建線程時,會使新創建的線程具有相同的NORM_PRIORITY優先級并且是非守護線程,同時也設置了線程的名稱。
  • handler:當線程池中線程數量達到maximumPoolSize時,仍有任務需要創建線程來完成,則handler采取相應的策略,有以下幾種策略:
    • ThreadPoolExecutor.AbortPolicy;//丟棄任務并拋出RejectedExecutionException異常(默認)。
    • ThreadPoolExecutor.DiscardPolicy;//不處理,直接丟棄任務。
    • ThreadPoolExecutor.DiscardOldestPolicy;//丟棄隊列里前面的任務,并執行當前任務
    • ThreadPoolExecutor.CallerRunsPolicy;//只用調用者所在線程來運行任務

????從ThreadPoolExecutor的源碼我們可以看到,ThreadPoolExecutor類繼承了抽象類AbstractExecutorService,而抽象類AbstractExecutorService實現了ExecutorService接口,ExecutorService接口又繼承了Executor接口。
????Executor是最頂層接口,在它里面只聲明了一個方法execute(Runnable),返回值為void,參數為Runnable類型,用來執行傳進去的任務的;

線程池的五種狀態

  • RUNNING:能接受新提交的任務,并且也能處理阻塞隊列中的任務;
  • SHUTDOWN:關閉狀態,不再接受新提交的任務,但卻可以繼續處理阻塞隊列中已保存的任務。在線程池處于 RUNNING 狀態時,調用 shutdown()方法會使線程池進入到該狀態.(finalize() 方法在執行過程中也會調用shutdown()方法進入該狀態);
  • STOP:不能接受新任務,也不處理隊列中的任務,會中斷正在處理任務的線程。在線程池處于 RUNNING 或 SHUTDOWN 狀態時,調用 shutdownNow() 方法會使線程池進入到該狀態;
  • TIDYING:如果所有的任務都已終止了,workerCount (有效線程數) 為0,線程池進入該狀態后會調用 terminated() 方法進入TERMINATED 狀態。
  • TERMINATED:在terminated() 方法執行完后進入該狀態,默認terminated()方法中什么也沒有做。
    進入TERMINATED的條件如下:
    1. 線程池不是RUNNING狀態;
    2. 線程池狀態不是TIDYING狀態或TERMINATED狀態;
    3. 如果線程池狀態是SHUTDOWN并且workerQueue為空;
    4. workerCount為0;
    5. 設置TIDYING狀態成功。


      線程池的狀態轉換過程圖.png

線程池執行流程

線程池執行流程圖.png
  • 當線程數量小于corePoolSize時,任務來時會創建新的線程來處理,并把該線程加入線程隊列中(實際上是一個HashSet)(此步驟需要獲取全局鎖,ReentryLock);
  • 如果當前線程數量達到了corePoolSize,任務來時將任務加入BlockingQueue;
  • 如果任務列隊滿了無法加入新的任務時,會創建新的線程(同樣需要獲取全局鎖);
  • 如果線程池數量達到maximumPoolSize,并且任務隊列已滿,新的任務將被拒絕;
    注意:獲取全局鎖是一個非常影響性能的因素,所以線程池會盡量執行第二步,因為此步驟不需要獲取全局鎖。

各種任務隊列(BlockingQueue)的區別

  • ArrayBlockingQueue: 基于數組實現的有界的阻塞隊列,該隊列按照FIFO(先進先出)原則對隊列中的元素進行排序。
  • LinkedBlockingQueue:基于鏈表實現的阻塞隊列,該隊列按照FIFO(先進先出)原則對隊列中的元素進行排序。吞吐量高于ArrayBlockingQueue,Executors.newFixedThreadPool()使用了該隊列。
  • SynchronousQueue:內部沒有任何容量的阻塞隊列。在它內部沒有任何的緩存空間。對于SynchronousQueue中的數據元素只有當我們試著取走的時候才可能存在。吞吐量高于LinkedBlockingQueue,Executors.newCachedThreadPool()使用了該隊列。
  • PriorityBlockingQueue:具有優先級的無限阻塞隊列。

RejectedExecutionHandler飽和策略

  • ThreadPoolExecutor.AbortPolicy:丟棄任務并拋出RejectedExecutionException異常(默認)。
  • ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列里前面的任務,并執行當前任務。
  • ThreadPoolExecutor.DiscardPolicy:不處理,直接丟棄任務。
  • ThreadPoolExecutor.CallerRunsPolicy:只用調用者所在線程來運行任務。

提交任務

  • 調用execute(Runnable command),無返回值。由于execute方法沒有返回值,所以說我們也就無法判定任務是否被線程池執行成功。
  • 調用submit(Runnable task),有返回值,返回類型是Future<?>類型。我們可以通過這個future來判斷任務是否執行成功,還可以通過future的get方法來獲取返回值。如果子線程任務沒有完成,get方法會阻塞住直到任務完成,而使用get(long timeout, TimeUnit unit)方法則會阻塞一段時間后立即返回,這時候有可能任務并沒有執行完。

線程池關閉

????shutdown()和shutdownNow()是用來關閉線程池的,都是調用了interruptIdleWorkers()方法去遍歷線程池中的工作線程,然后去打斷它們。
????shutdown原理:將線程池狀態設置成SHUTDOWN狀態,然后中斷所有沒有正在執行任務的線程。
????shutdownNow原理:將線程池的狀態設置成STOP狀態,然后中斷所有任務(包括正在執行的)的線程,并返回等待執行任務的列表。
????中斷采用interrupt方法,所以無法響應中斷的任務可能永遠無法終止。但調用上述的兩個關閉之一,isShutdown()方法返回值為true,當所有任務都已關閉,表示線程池關閉完成,則isTerminated()方法返回值為true。當需要立刻中斷所有的線程,不一定需要執行完任務,可直接調用shutdownNow()方法。

幾種常用的線程池

CachedThreadPool

????創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。

/**
 * Creates a thread pool that creates new threads as needed, but
 * will reuse previously constructed threads when they are
 * available.  These pools will typically improve the performance
 * of programs that execute many short-lived asynchronous tasks.
 * Calls to {@code execute} will reuse previously constructed
 * threads if available. If no existing thread is available, a new
 * thread will be created and added to the pool. Threads that have
 * not been used for sixty seconds are terminated and removed from
 * the cache. Thus, a pool that remains idle for long enough will
 * not consume any resources. Note that pools with similar
 * properties but different details (for example, timeout parameters)
 * may be created using {@link ThreadPoolExecutor} constructors.
 *
 * @return the newly created thread pool
*/
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

????從java.util.concurrent.Executors的源碼中我們可以發現,CachedThreadPool使用了SynchronousQueue這個沒有容量的阻塞隊列,線程池的coreSize為0,maxSize為無限大。

特點:

  • 工作線程的創建數量幾乎沒有限制, 這樣可靈活的往線程池中添加線程。
  • 如果長時間沒有往線程池中提交任務,即如果工作線程空閑了指定的時間(默認為1分鐘),則該工作線程將自動終止。終止后,如果你又提交了新的任務,則線程池重新創建一個工作線程。
  • 在使用CachedThreadPool時,一定要注意控制任務的數量,否則,如果當生產者提供任務的速度大于消費者處理任務的時候,可能會無限創建線程,從而導致系統資源(CPU,內存等)耗竭。

使用示例:

public class CachedThreadPool {
    public static void main(String[] args) {
        ExecutorService cacheThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            cacheThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(index);
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

????程序一次性輸出所有的數字,因為可以創建多個線程同時執行任務

FixedThreadPool

????創建一個指定工作線程數量的線程池。每當提交一個任務就創建一個工作線程,如果工作線程數量達到線程池初始的最大數,則將提交的任務存入到阻塞隊列中。

/**
 * Creates a thread pool that reuses a fixed number of threads
 * operating off a shared unbounded queue.  At any point, at most
 * {@code nThreads} threads will be active processing tasks.
 * If additional tasks are submitted when all threads are active,
 * they will wait in the queue until a thread is available.
 * If any thread terminates due to a failure during execution
 * prior to shutdown, a new one will take its place if needed to
 * execute subsequent tasks.  The threads in the pool will exist
 * until it is explicitly {@link ExecutorService#shutdown shutdown}.
 *
 * @param nThreads the number of threads in the pool
 * @return the newly created thread pool
 * @throws IllegalArgumentException if {@code nThreads <= 0}
 */
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

????從java.util.concurrent.Executors的源碼中我們可以發現,FixedThreadPool線程池的corePoolSize和maximumPoolSize數量是一樣的,且使用的LinkedBlockingQueue是無界阻塞隊列,因此達到corePoolSize后不會繼續創建線程而是阻塞在隊列那了。

特點:

  • 所容納最大的線程數就是我們設置的核心線程數。
  • 如果線程池的線程處于空閑狀態的話,它們并不會被回收,除非是這個線程池被關閉。如果所有的線程都處于活動狀態的話,新任務就會處于等待狀態,直到有線程空閑出來。
  • 由于FixedThreadPool只有核心線程,并且這些線程都不會被回收,也就是它能夠更快速的響應外界請求。
  • 阻塞隊列的大小沒有限制。

使用示例:

public class FixedThreadPool {
    public static void main(String[] args) {
        ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            final int index = i;
            newFixedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(index);
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

????因為線程池的大小為5,每個任務輸出index后sleep 2秒,所以每2秒打印5個數字。

SingleThreadExecutor

????創建一個單線程化的Executor,即只創建唯一的工作者線程來執行任務,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。如果這個線程異常結束,會有另一個取代它,保證順序執行。單工作線程最大的特點是可保證順序地執行各個任務,并且在任意給定的時間不會有多個線程是活動的。

/**
 * Creates an Executor that uses a single worker thread operating
 * off an unbounded queue. (Note however that if this single
 * thread terminates due to a failure during execution prior to
 * shutdown, a new one will take its place if needed to execute
 * subsequent tasks.)  Tasks are guaranteed to execute
 * sequentially, and no more than one task will be active at any
 * given time. Unlike the otherwise equivalent
 * {@code newFixedThreadPool(1)} the returned executor is
 * guaranteed not to be reconfigurable to use additional threads.
 *
 * @return the newly created single-threaded Executor
 */
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

????從java.util.concurrent.Executors的源碼中我們可以發現,SingleThreadExecutor線程池的corePoolSize和maximumPoolSize數量都是1,即線程池只創建一個線程,且使用的LinkedBlockingQueue是無界阻塞隊列,因此如果線程池中的線程處于活動的,則后面的任務只能到阻塞隊列中。
特點:

  • 只創建唯一的工作者線程來執行任務,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。
  • 任務隊列沒有大小限制,也就意味著這一個任務處于活動狀態時,其他任務都會在任務隊列中排隊等候依次執行。
  • 所有的外界任務統一到一個線程中支持,所以在這個任務執行之間我們不需要處理線程同步的問題。

使用示例:

public class SingleThreadExecutor {
    public static void main(String[] args) {
        ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            cacheThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(index);
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

????程序每隔兩秒依次輸出一個數字。

ScheduleThreadPool

????創建一個定長的線程池,而且支持定時的以及周期性的任務執行。

/**
 * Creates a thread pool that can schedule commands to run after a
 * given delay, or to execute periodically.
 * @param corePoolSize the number of threads to keep in the pool,
 * even if they are idle
 * @return a newly created scheduled thread pool
 * @throws IllegalArgumentException if {@code corePoolSize < 0}
 */
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
/**
 * Creates a new {@code ScheduledThreadPoolExecutor} with the
 * given core pool size.
 *
 * @param corePoolSize the number of threads to keep in the pool, even
 *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
 * @throws IllegalArgumentException if {@code corePoolSize < 0}
 */
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

????從java.util.concurrent.Executors的源碼即ScheduledThreadPoolExecutor的構造函數我們可以發現,ScheduleThreadPool的核心線程數是固定的,對于非核心線程幾乎可以說是沒有限制的,并且當非核心線程處于閑置狀態的時候就會立即被回收。

特點:

  • 核心線程數固定,且當非核心線程處于閑置狀態會立即被回收。
  • 可創建定時執行和延遲執行任務

使用示例:

延遲2秒執行任務
public class ScheduleThreadPool {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
        for (int i = 0; i < 10; i++) {
            final int index = i;
            scheduledExecutorService.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println(index);
                }
            }, 2, TimeUnit.SECONDS);
        }
    }
}
延遲2秒執行,每5秒周期執行任務
public class ScheduleThreadPool {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
        for (int i = 0; i < 10; i++) {
            final int index = i;
            scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    System.out.println(index);
                }
            }, 2,5, TimeUnit.SECONDS);
        }
    }
}

Executors各個方法的弊端:

  • newFixedThreadPool和newSingleThreadExecutor:主要問題是堆積的請求處理隊列可能會耗費非常大的內存,甚至OOM。
  • newCachedThreadPool和newScheduledThreadPool:主要問題是線程數最大數是Integer.MAX_VALUE,可能會創建數量非常多的線程,甚至OOM。

總結

  • 在使用線程池中,建議手動創建線程池,盡量不要使用Executors去創建,而是通過ThreadPoolExecutor的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。
  • 使用線程池是需要針對具體情況而具體處理,不同的任務類別應采用不同規模的線程池,任務類別可劃分為CPU密集型任務、IO密集型任務和混合型任務。以下N為CPU個數。
    • 對于CPU密集型任務:線程池中線程個數應盡量少,如配置N+1個線程的線程池;
    • 對于IO密集型任務:由于IO操作速度遠低于CPU速度,那么在運行這類任務時,CPU絕大多數時間處于空閑狀態,那么線程池可以配置盡量多些的線程,以提高CPU利用率,如2*N;
    • 對于混合型任務:可以拆分為CPU密集型任務和IO密集型任務,當這兩類任務執行時間相差無幾時,通過拆分再執行的吞吐率高于串行執行的吞吐率,但若這兩類任務執行時間有數據級的差距,那么沒有拆分的意義。

線程池面試

什么是線程池

????創建一組可供管理的線程,它關注的是如何縮短或調整線程的創建與銷毀所消費時間的技術,從而提高服務器程序性能的。
????它把線程的創建與銷毀分別安排在服務器程序的啟動和結束的時間段或者一些空閑的時間段以減少服務請求是去創建和銷毀線程的時間。
????它還顯著減少了創建線程的數目。

為什么要使用線程池

????當我們在使用線程時,如果每次需要一個線程時都去創建一個線程,這樣實現起來很簡單,但是會有一個問題:當并發線程數過多時,并且每個線程都是執行一個時間很短的任務就結束時,這樣創建和銷毀線程的時間要比花在實際處理任務的時間要長的多,在一個JVM里創建太多的線程可能會導致由于系統過度消耗內存或切換過度導致系統資源不足而導致OOM問題。????線程池為線程生命周期開銷問題和資源不足問題提供了解決方案。通過對多個任務重用線程,線程創建的開銷被分攤到了多個任務上。

如何正確的使用線程池

  • 不要對那些同步等待其它任務結果的任務排隊。這可能會導致上面所描述的那種形式的死鎖,在那種死鎖中,所有線程都被一些任務所占用,這些任務依次等待排隊任務的結果,而這些任務又無法執行,因為沒有空閑的線程可以使用。
  • 在為任務時間可能很長的線程使用合用的線程時要小心。如果程序必須等待諸如 I/O 完成這樣的某個資源,那么請指定最長的等待時間,以及隨后是失效還是將任務重新排隊以便稍后執行。
  • 理解任務。要有效地調整線程池大小,需要理解正在排隊的任務以及它們正在做什么。它們是 CPU 限制的嗎?它們是 I/O 限制的嗎?你的答案將影響如何調整應用程序。如果有不同的任務類,這些類有著截然不同的特征,那么為不同任務類設置多個工作隊列可能會有意義,這樣可以相應地調整每個池。

ThreadPoolExecutor構造函數中幾個重要參數的解釋

  • corePoolSize:核心池大小,即線程的數量。
  • maximumPoolSize:線程池最大線程數,表示在線程池中最多能創建多少個線程。
  • keepAliveTime:表示線程沒有任務執行時最多保持多久時間會終止。
  • unit:參數keepAliveTime的時間單位,有7種取值,具體可查看前面章節。
  • workQueue:線程池采用的緩沖隊列,用來存儲等待執行的任務。
  • threadFactory:線程工廠,主要用來創建線程。
  • handler:線程的阻塞策略。當線程池中線程數量達到maximumPoolSize時,仍有任務需要創建線程來完成,則handler采取相應的策略。

線程池的狀態

  • RUNNING:能接受新提交的任務,并且也能處理阻塞隊列中的任務;
  • SHUTDOWN:關閉狀態,不再接受新提交的任務,但卻可以繼續處理阻塞隊列中已保存的任務。在線程池處于 RUNNING 狀態時,調用 shutdown()方法會使線程池進入到該狀態.(finalize() 方法在執行過程中也會調用shutdown()方法進入該狀態);
  • STOP:不能接受新任務,也不處理隊列中的任務,會中斷正在處理任務的線程。在線程池處于 RUNNING 或 SHUTDOWN 狀態時,調用 shutdownNow() 方法會使線程池進入到該狀態;
  • TIDYING:如果所有的任務都已終止了,workerCount (有效線程數) 為0,線程池進入該狀態后會調用 terminated() 方法進入TERMINATED 狀態。
  • TERMINATED:在terminated() 方法執行完后進入該狀態,默認terminated()方法中什么也沒有做。

線程池的執行流程

  • 當線程數量小于corePoolSize時,任務來時會創建新的線程來處理,并把該線程加入線程隊列中(實際上是一個HashSet)(此步驟需要獲取全局鎖,ReentryLock);
  • 如果當前線程數量達到了corePoolSize,任務來時將任務加入BlockingQueue;
  • 如果任務列隊滿了無法加入新的任務時,會創建新的線程(同樣需要獲取全局鎖);
  • 如果線程池數量達到maximumPoolSize,并且任務隊列已滿,新的任務將被拒絕;

常用的幾種線程池

  • CachedThreadPool: 創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。
  • FixedThreadPool:創建一個指定工作線程數量的線程池。每當提交一個任務就創建一個工作線程,如果工作線程數量達到線程池初始的最大數,則將提交的任務存入到阻塞隊列中。
  • SingleThreadExecutor:創建一個單線程化的Executor,即只創建唯一的工作者線程來執行任務,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。
  • ScheduleThreadPool:創建一個定長的線程池,而且支持定時的以及周期性的任務執行,支持定時及周期性任務執行。

????整理文章主要為了自己日后復習用,文章中可能會引用到別的博主的文章內容,如涉及到博主的版權問題,請博主聯系我。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,428評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,024評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,285評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,548評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,328評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,878評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,971評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,098評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,616評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,554評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,725評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,243評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,971評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,361評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,613評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,339評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,695評論 2 370

推薦閱讀更多精彩內容