Java線程池_ThreadPoolExecutor原理分析

線程池中有一定數量的工作線程,工作線程會循環從任務隊列中獲取任務,并執行這個任務。那么怎么去停止這些工作線程呢?
這里就涉及到線程池兩個重要概念:工作線程數量和線程池狀態。

一.線程池狀態和工作線程數量

這本來是兩個不同的概念,但是在ThreadPoolExecutor中我們使用一個變量ctl來存儲這兩個值,這樣我們只需要維護這一個變量的并發問題,提高運行效率。

    /**
     * 記錄線程池中Worker工作線程數量和線程池的狀態
     * int類型是32位,它的高3位,表示線程池的狀態,低29位表示Worker的數量
     */
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    // COUNT_BITS 29位,
    private static final int COUNT_BITS = Integer.SIZE - 3;
    // 表示線程池中創建Worker工作線程數量的最大值。即 0b0001.....1(29位1)
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

怎么使用一個變量ctl存儲兩個值呢?
就是利用int變量的高3位來儲存線程池狀態,用int變量的低29位來儲存工作線程數量。

這樣就有兩個需要注意的地方:

  1. 工作線程數量最大值不能超過int類型29位的值CAPACITY 即0b0001.....1(29位1)
  2. 因為線程池狀態都是高3位儲存的,所以工作線程數量不會影響狀態值大小關系。

1.1 線程池狀態

    // 高3位值是111
    private static final int RUNNING    = -1 << COUNT_BITS;
    // 高3位值是000
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    // 高3位值是001
    private static final int STOP       =  1 << COUNT_BITS;
    // 高3位值是010
    private static final int TIDYING    =  2 << COUNT_BITS;
    // 高3位值是011
    private static final int TERMINATED =  3 << COUNT_BITS;

線程池狀態分析:

  1. RUNNING狀態:線程池剛創建時的狀態。向任務隊列中添加任務,并執行任務隊列中的任務。因為高3位值是111,即處于RUNNING狀態下的ctl值都是負數。
  2. SHUTDOWN狀態: 調用shutdown方法,會將線程池設置成這個狀態。不能向任務隊列中添加任務,但是可以執行任務隊列中已添加的任務。并且處于SHUTDOWN狀態下正在運行任務的工作線程不能中斷的,就是保證任務能夠執行完成。
  3. STOP狀態: 調用shutdownNow方法,會將線程池設置成這個狀態。不能向任務隊列中添加任務,也不能再執行任務隊列中已添加的任務。
  4. TIDYING狀態: 調用tryTerminate方法,可能會將線程池設置成這個狀態。這個只是中斷過度狀態,表示線程池即將變成TERMINATED狀態。
  5. TERMINATED狀態: 調用tryTerminate方法,可能會將線程池設置成這個狀態。表示線程池已經完全終止,即任務隊列為空,工作線程數量也是0.

線程池為什么要定義這么多狀態呢?按道理說線程池只應該有運行和終止這兩種狀態啊。
主要是因為終止線程池時,要考慮正在執行的任務和已經添加到任務隊列中待執行的任務該如何處理,否則的話,這些任務可能就會被丟失。

線程池提供了兩個方式處理:

  1. shutdown方法: 它會將線程池狀態變成SHUTDOWN 狀態。禁止向添加新的任務,但是會讓任務隊列中的任務繼續執行,最后釋放所有的工作線程,讓線程池狀態變成TERMINATED狀態。
  2. shutdownNow方法: 它會將線程池狀態變成STOP 狀態。禁止向添加新的任務,也不會執行任務隊列中的任務,但是會返回這個任務集合,釋放所有的工作線程,讓線程池狀態變成TERMINATED狀態。

1.2 操作ctl的方法

1.2.1 獲取線程池的狀態

    /**
     * 獲取線程池的狀態。因為線程池的狀態是使用高3位儲存,所以屏蔽低29位就行了。
     * 所以就c與~CAPACITY(0b1110..0)進行&操作,屏蔽低29位的值了。
     * 注意:這里是屏蔽低29位的值,而不是右移29位。
     */
    private static int runStateOf(int c)     { return c & ~CAPACITY; }

1.2.2 獲取工作線程數量

    /**
     * 獲取線程池中Worker工作線程的數量,
     * 因為只使用低29位保存Worker的數量,只要屏蔽高3位的值就行了
     * 所以就c與CAPACITY(0b0001...1)進行&操作,屏蔽高3位的值了。
     */
    private static int workerCountOf(int c)  { return c & CAPACITY; }

1.2.3 合并ctl的值

    /**
     * 得到ctl的值。
     * 接受兩個參數rs和wc。rs表示線程池的狀態,wc表示Worker工作線程的數量。
     * 對于rs來說我們只需要高3位的值,對于wc來說我們需要低29位的值。
     * 所以我們將rs | wc就可以得到ctl的值了。
     */
    private static int ctlOf(int rs, int wc) { return rs | wc; }

1.2.4 其他方法

    // 因為RUNNING狀態高三位是111,所以狀態值rs與工作線程數量ws相與的結果值c一定是個負數,
    // 而其他狀態值都是大于等于0的數,所以c是負數,那么表示當前線程處于運行狀態。
    private static boolean isRunning(int c) {
        return c < SHUTDOWN;
    }

    /**
     * 使用CAS函數將ctl值自增
     */
    private boolean compareAndIncrementWorkerCount(int expect) {
        return ctl.compareAndSet(expect, expect + 1);
    }

    /**
     * 使用CAS函數將ctl值自減
     */
    private boolean compareAndDecrementWorkerCount(int expect) {
        return ctl.compareAndSet(expect, expect - 1);
    }
    /**
     * 使用CAS函數加循環方法這種樂觀鎖的方式,解決并發問題。
     * 保證使ctl值減一
     */
    private void decrementWorkerCount() {
        do {} while (! compareAndDecrementWorkerCount(ctl.get()));
    }

二. 重要成員變量

    // 記錄線程池中Worker工作線程數量和線程池的狀態
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

    // 任務線程的阻塞隊列,因為是阻塞隊列,所以它是并發安全的
    private final BlockingQueue<Runnable> workQueue;

    // 獨占鎖,用來保證操作成員變量的并發安全問題
    private final ReentrantLock mainLock = new ReentrantLock();

    // 等待線程池完全終止的條件Condition,
    private final Condition termination = mainLock.newCondition();

    //-----------------  需要mainLock來保證并發安全-------------------------//
    // 線程池中工作線程集合。Worker中持有線程thread變量
    private final HashSet<Worker> workers = new HashSet<Worker>();

    // 線程池中曾擁有過的最大工作線程個數
    private int largestPoolSize;

    // 線程池完成過任務的總個數
    private long completedTaskCount;
    //-----------------  需要mainLock來保證并發安全-------------------------//

    // 創建線程的工廠類
    private volatile ThreadFactory threadFactory;

    // 當任務被拒絕時,用來處理這個被拒絕的任務
    private volatile RejectedExecutionHandler handler;
    // 工作線程空閑的超時時間keepAliveTime
    private volatile long keepAliveTime;

    // 是否允許核心池線程超時釋放
    private volatile boolean allowCoreThreadTimeOut;

    // 線程池核心池線程個數
    private volatile int corePoolSize;

    // 線程池最大的線程個數
    private volatile int maximumPoolSize;

成員變量的含義已經標注了:

  1. mainLock:使用mainLock來保證會發生變化成員變量的并發安全問題。會發生的成員變量有5個:ctl、workQueue、workers、largestPoolSize和completedTaskCount。但是其中ctl和workQueue的類型本身就是多線程安全的,所以不用mainLock鎖保護。
  2. termination:等待線程池完全終止的條件,如果線程池沒有完全終止,調用它的awaitNanos方法,讓線程等待。當線程池完全終止后,調用它的signalAll方法,喚醒所有等待termination條件的線程。
  3. workers:記錄所有的工作線程Worker
  4. workQueue:記錄所有待執行的任務。使用阻塞隊列BlockingQueue,可以在隊列為空時,線程等待,隊列有值時,喚醒等待的線程。
  5. largestPoolSize:線程池中曾擁有過的最大工作線程個數
  6. completedTaskCount:線程池完成過任務的總個數
  7. threadFactory:創建線程的工廠類
  8. handler:當任務被拒絕時,用來處理這個被拒絕的任務
  9. keepAliveTime:工作線程允許空閑的超時時間,一般都是針對超過核心池數量的工作線程。
  10. allowCoreThreadTimeOut: 是否允許核心池的工作線程超時釋放。
  11. corePoolSize:線程池核心池線程個數。
  12. maximumPoolSize: 線程池最大的線程個數。

這里注意一下兩個概念核心池個數和最大線程池個數:

  1. 核心池個數就是線程池能夠維持的常用工作線程個數,當工作線程沒有執行任務空閑時,它不會被銷毀,而是在等待。但是如果設置allowCoreThreadTimeOut為true,那么核心池工作線程也是會被銷毀。
  2. 最大線程池個數就是線程池允許開啟的最大工作線程個數。最大線程池的意義就是當核心池的工作線程不夠用,且任務隊列也已經滿了,不能添加新的任務了,那么就要開啟新的工作線程來執行任務。

三. 執行任務execute方法

在線程池中如何執行一個任務command,要分三種情況:

  1. 線程池中工作線程的數量沒有達到核心池個數,那么線程池就應該開啟新的工作線程來執行任務。
  2. 線程池中工作線程的數量達到核心池個數,那么就應該將任務添加到任務隊列中,等待著工作線程去任務隊列中獲取任務并執行。
  3. 如果任務添加到任務隊列失敗,那么就要開啟新的工作線程來執行任務。

    public void execute(Runnable command) {
        // 如果command為null,拋出異常
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 分為三個步驟:
         * 1. 如果運行的工作線程數量少于核心池數量corePoolSize,
         * 那么就調用addWorker方法開啟一個新的工作線程,運行任務command。
         * 2. 如果開啟新的工作線程失敗,就將任務添加到任務隊列中。
         * 3. 添加到任務隊列失敗,
         * 那么仍然addWorker方法在最大池中開啟一個新的工作線程,運行任務command。     
         */
        int c = ctl.get();
        // 運行的工作線程數量少于核心池數量corePoolSize
        if (workerCountOf(c) < corePoolSize) {
            /**
             * 開啟一個新的工作線程,運行任務command。
             * 返回true,表示開啟工作線程成功,直接return。
             * 返回false,表示沒有開啟新線程。那么任務command就沒有運行,所以要執行下面代碼。
              */
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        // 線程池處于運行狀態,
        // 且任務添加到任務阻塞隊列workQueue中成功,即workQueue隊列有剩余空間。
        if (isRunning(c) && workQueue.offer(command)) {
            // 再次檢查線程池狀態和工作線程數量
            int recheck = ctl.get();
            /**
             * 如果線程池不在運行狀態,那么就調用remove方法移除workQueue隊列這個任務command,
             * 如果移除成功,那么調用reject(command)方法,進行拒絕任務的處理。
             * 如果移除失敗,那么這個任務還是會被執行,那么就不用調用reject(command)方法
             */
            if (! isRunning(recheck) && remove(command))
                reject(command);
            // 如果工作線程數量為0,但是workQueue隊列中我們添加過任務,
            // 那么必須調用addWorker方法,開啟一個新的工作線程。
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        // 調用addWorker方法,開啟一個新的工作線程,運行任務command。
        // 如果還是失敗,那么這個任務command就不會不可能執行了,
        // 那么調用reject(command)方法拒絕這個任務
        else if (!addWorker(command, false))
            reject(command);
    }

方法流程上面已經有標注,注意有以下幾點:

  1. addWorker(Runnable firstTask, boolean core):表示開啟一個新的工作線程執行任務firstTask。core是用來判斷核心池還是最大池。返回false,表示開啟新線程失敗,即任務firstTask沒有機會執行。
  2. isRunning(c)線程池處于RUNNING狀態,只有處于RUNNING狀態下,才能將任務添加到任務隊列。
  3. reject(command) 當任務command不能在線程池中執行時,就會調用這個方法,告訴調用值,線程池拒絕執行這個任務。

四. 添加工作線程addWorker方法

就是利用任務task創建一個新的工作線程Work,然后將它添加到工作線程集合workers中。但是需要注意多線程并發問題。

    private boolean addWorker(Runnable firstTask, boolean core) {
        // 利用死循環和CAS函數,實現樂觀鎖,來實現多線程改變ctl值的并發問題
        // 因為ctl值代表兩個東西,工作線程數量和線程池狀態。
        // 這里就用了兩個for循環,一個是線程池狀態的for循環,一個是工作線程數量的for循環
        retry:
        for (;;) {
            int c = ctl.get();
            // 獲取線程池運行狀態rs,
            int rs = runStateOf(c);

            // 首先判斷線程池狀態和任務隊列狀態,
            // 來判斷能否創建新的工作線程
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
            
            for (;;) {
                // 線程池中工作線程數量wc
                int wc = workerCountOf(c);
                // 當線程池工作線程數量wc大于線程上限CAPACITY,
                // 或者用戶規定核心池數量corePoolSize或用戶規定最大線程池數量maximumPoolSize
                // 表示不能創建工作線程了,所以返回false
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                // 使用CAS函數,使工作線程數量wc加一
                if (compareAndIncrementWorkerCount(c))
                    // 跳出retry循環
                    break retry;
                // 來到這里表示CAS函數失敗,那么就要循環重新判斷
                // 但是c還代表線程狀態,如果線程狀態改變,那么就必須跳轉到retry循環
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        // 工作線程是否開始,即調用了線程的start方法
        boolean workerStarted = false;
        // 工作線程是否添加到工作線程隊列workers中
        boolean workerAdded = false;
        Worker w = null;
        try {
            // 創建一個Worker對象
            w = new Worker(firstTask);
            // 得到Worker所擁有的線程thread
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                // 并發鎖
                mainLock.lock();
                try {
                    //  獲取線程池運行狀態rs
                    int rs = runStateOf(ctl.get());

                    // 當線程池是運行狀態,或者是SHUTDOWN狀態但firstTask為null,
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        // 如果線程t已經被開啟,就拋出異常
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        // 將w添加到工作線程集合workers中
                        workers.add(w);
                        // 獲取工作線程集合workers的個數
                        int s = workers.size();
                        // 記錄線程池歷史最大的工作線程個數
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                // 如果已經添加到工作線程隊列中,那么開啟線程
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            // 如果開啟工作線程失敗,那么這個任務也就沒有執行
            // 因此移除這個任務w(如果隊列中有),減少工作線程數量,因為這個數量在之前已經增加了
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

添加一個新的工作線程,就涉及到兩個成員變量的改變,一個是工作線程數量ctl,一個是工作線程集合workers。而ctl的類型是AtomicInteger,所以它可以使用樂觀鎖解決并發問題,workers就只能使用mainLock互斥鎖來保證并發安全問題。

4.1 更改工作線程數量ctl

因為ctl儲存了兩個值,工作線程數量和線程池狀態。所以使用了兩個for循環來監控多線程對這兩個值的更改。
用線程池狀態來判斷是否允許添加新的工作線程:

            //  是對addWorker中線程狀態if判斷的拆分
            // 當線程池不是處于運行狀態
            if (rs >= SHUTDOWN) {
                /**
                 * 線程池狀態不是SHUTDOWN,或者firstTask不為null,或者任務隊列為空,
                 * 都直接返回false,表示開啟新工作線程失敗。
                 * 只有當線程池狀態是SHUTDOWN,firstTask為null,任務隊列不為空時,
                 * 需要創建新的工作線程。
                 * 從execute(Runnable command)方法中分析,firstTask參數為空只有一種情況,
                 * 此時線程池中工作線程數量是0,而任務隊列不為空,
                 * 那么就要開啟一個新工作線程去執行任務隊列中的任務,否則這些任務會被丟失。
                 */
                if (rs != SHUTDOWN || firstTask != null || workQueue.isEmpty()) {
                    return false;
                }
            }

由此可以得出,只有兩種情形允許添加新的工作線程:

  1. 線程池處于RUNNING狀態
  2. 線程池雖然處于SHUTDOWN狀態,但是線程池工作線程個數是0(即這里的firstTask != null),且任務隊列workQueue不為空,那么就要開啟一個新工作線程去執行任務隊列中的任務。

然后使用for循環和CAS函數方式,來給工作線程數量加一。注意此時工作線程還沒有創建,并添加到線程集合workers中,所以如果線程添加失敗,那么還要將工作線程數量減一。

4.2 添加工作線程集合workers

創建一個工作線程Worker,將它添加到線程集合workers中,然后開啟這個工作線程,使用mainLock獨占鎖保證成員變量workers的并發安全問題。

五. 內部類Worker

    private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        private static final long serialVersionUID = 6138294804551838833L;

        /** 該Worker所擁有的工作線程 */
        final Thread thread;
        /** Worker擁有的第一個任務,初始化的時候賦值 */
        Runnable firstTask;
        /** 該工作線程Worker完成任務的數量 */
        volatile long completedTasks;

        /**
         * Creates with given first task and thread from ThreadFactory.
         * @param firstTask the first task (null if none)
         */
        Worker(Runnable firstTask) {
            // 將state設置為-1,禁止發起中斷請求,
            // 直到調用過runWorker方法,即線程已經運行時。
            setState(-1);
            // 第一個任務
            this.firstTask = firstTask;
            // 創建一個thread線程對象,它的run方法就是本Worker的run方法
            // 這個thread就是Worker真正執行任務的工作線程
            this.thread = getThreadFactory().newThread(this);
        }

        /** 復寫的是Runnable中的run方法,所以當工作線程開啟運行后,會調用這個方法。  */
        public void run() {
            runWorker(this);
        }

        // 當前獨占鎖是否空閑
        protected boolean isHeldExclusively() {
            return getState() != 0;
        }

        // 嘗試獲取獨占鎖
        protected boolean tryAcquire(int unused) {
            // 如果通過CAS函數,可以將state值從0改變成1,那么表示獲取獨占鎖成功。
            // 否則獨占鎖被別的線程獲取了。
            if (compareAndSetState(0, 1)) {
                // 設置擁有獨占鎖的線程是當前線程
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        // 釋放獨占鎖
        protected boolean tryRelease(int unused) {
            // 設置擁有獨占鎖的線程為null
            setExclusiveOwnerThread(null);
            // 設置獲取獨占鎖的次數是0,表示鎖是空閑狀態
            setState(0);
            return true;
        }

        // 獲取獨占鎖,如果鎖被別的獲取,就一直等待。
        public void lock()        { acquire(1); }
        // 嘗試獲取獨占鎖,如果鎖被別的獲取,就直接返回false,表示獲取失敗。
        public boolean tryLock()  { return tryAcquire(1); }
        // 釋放獨占鎖
        public void unlock()      { release(1); }
        // 當前獨占鎖是否空閑
        public boolean isLocked() { return isHeldExclusively(); }

        // 如果Worker的工作線程thread已經開啟,那么發起中斷請求。
        void interruptIfStarted() {
            Thread t;
            // getState() >= 0表示thread已經開啟
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }
    }

Worker實現了Runnable接口,那么就可以通過Worker對象創建一個新線程thread,這個thread就是Worker的工作線程,而任務都在run方法中執行。
Worker還繼承自AbstractQueuedSynchronizer類。我們知道可以通過AQS類實現獨占鎖和共享鎖,而Worker中實現了tryAcquire和tryRelease方法,說明Worker對象也是個獨占鎖對象。我們可以考慮一下Worker這個獨占鎖的作用是什么?在后面會介紹到。

六. 工作線程運行任務runWorker方法

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        // 將w的state狀態設置成0,這樣就允許對w的thread線程進行中斷請求了。
        w.unlock();
        // completedAbruptly表示線程突然終結
        boolean completedAbruptly = true;
        try {
            // 通過getTask從任務隊列中獲取任務task執行,這個方法是個阻塞方法。
            while (task != null || (task = getTask()) != null) {
                // 獲取w獨占鎖,保證當本工作線程運行任務時,
                // 不能對該線程進行中斷請求。
                w.lock();
                /**
                 * 如果線程池大于STOP狀態,且Worker工作線程中斷標志位是false,
                 * 那么就調用wt的interrupt方法發起中斷請求。
                 */
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    // Worker工作線程發起中斷請求
                    wt.interrupt();
                try {
                    // 鉤子方法,提供給子類。在執行任務之前調用
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        // 調用run方法,執行任務
                        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,進行下一次循環
                    task = null;
                    // 將work完成的任務數completedTasks加一
                    w.completedTasks++;
                    // 釋放w獨占鎖
                    w.unlock();
                }
            }
            // completedAbruptly = false表示線程正常完成終結
            completedAbruptly = false;
        } finally {
            // 進行一個工作線程完結后的后續操作
            processWorkerExit(w, completedAbruptly);
        }
    }

runWorker方法是在每個工作線程的run方法中調用,通過getTask()方法從任務隊列中獲取任務task執行,這個方法可以阻塞當前工作線程,如果getTask()方法返回null,那么工作線程就會運行結束,釋放線程。

雖然runWorker方法運行在每個工作線程中,但是對于一個Worker來說,只會有它的工作線程能夠運行runWorker方法,而且改變的也是這個Worker的成員變量,且這些成員變量也只能在runWorker方法改變,那么它沒有多線程并發問題啊,那么為什么在這里加鎖呢?
這是因為Worker中有一個變量是可以被其他線程改變的,就是它的工作線程thread的中斷請求,所以Worker獨占鎖的作用就是控制別的線程對它的工作線程thread中斷請求的。

最后調用processWorkerExit方法,進行一個工作線程完結后的后續操作。

七. 獲取任務getTask方法

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

        for (;;) {
            int c = ctl.get();
            // 獲取線程池狀態rs
            int rs = runStateOf(c);

            // 如果有需要檢查任務隊列workQueue是否為空
            // 即rs >= STOP或者rs == SHUTDOWN且workQueue為空,那么返回null,停止工作線程
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                // 將工作線程數量減一
                decrementWorkerCount();
                return null;
            }

            // 獲取工作線程數量wc
            int wc = workerCountOf(c);

            /**
             * 如果allowCoreThreadTimeOut為true或者wc > corePoolSize時,
             * 就要減少工作線程數量了。
             * 當工作線程在keepAliveTime時間內,沒有獲取到可執行的任務,
             * 那么該工作線程就要被銷毀。
             */
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                // 工作線程數量減一,返回null,銷毀工作線程。
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                // 從任務隊列workQueue中獲取了任務r,會阻塞當前線程。
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                // 如果r不為null,返回這個任務r
                if (r != null)
                    return r;
                // r是null,表示獲取任務超時
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

從阻塞任務隊列workQueue中獲取任務返回,因為是阻塞任務隊列,所以可以阻塞當前線程。如果返回null,那么會完結調用getTask方法的那個工作線程。那么getTask方法在什么情況下返回null呢?

  1. 線程池的狀態大于等于STOP,或者線程狀態是SHUTDOWN且當前任務隊列為空,那么返回null,停止工作線程。
  2. 獲取任務時間超時,那么也會返回null,停止工作線程。因為線程池一般只維護一定數量的工作線程,如果超過這個數量,那么超過數量的工作線程,在空閑一定時間后,應該被釋放。

八. 終止線程池的方法

8.1 shutdown和shutdownNow方法

    /**
     * 終止線程池。不能在添加新任務了,但是已經添加到任務隊列的任務還是會執行。
     * 且對所有不是正在執行任務的工作線程都發起中斷請求
     */
    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // 檢查是否擁有Shutdown的權限
            checkShutdownAccess();
            // 將線程池狀態變成SHUTDOWN狀態
            advanceRunState(SHUTDOWN);
            // 對所有不是正在執行任務的工作線程都發起中斷請求
            interruptIdleWorkers();
            // 鉤子方法,提供給子類實現。表示線程池已經shutdown了
            onShutdown();
        } finally {
            mainLock.unlock();
        }
        // 嘗試去終結線程池
        tryTerminate();
    }

    /**
     * 終止線程池。不能在添加新任務了,也不會執行已經添加到任務隊列的任務,只是將這些任務返回。
     * 且對所有工作線程都發起中斷請求, 不管這個工作線程是否正在執行任務
     */
    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // 檢查是否擁有Shutdown的權限
            checkShutdownAccess();
            // 將線程池狀態變成STOP狀態
            advanceRunState(STOP);
            // 對所有工作線程都發起中斷請求, 不管這個工作線程是否正在執行任務
            interruptWorkers();
            // 返回阻塞隊列workQueue中未執行任務的集合
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        // 嘗試去終結線程池
        tryTerminate();
        return tasks;
    }

shutdown和shutdownNow區別:

  1. shutdown方法將線程池設置成SHUTDOWN狀態,shutdownNow將線程池設置成STOP狀態。
  2. shutdown方法調用之后不能在添加新任務了,但是已經添加到任務隊列的任務還是會執行。shutdownNow方法調用之后不能在添加新任務了,也不會執行已經添加到任務隊列的任務,只是將這些任務返回。
  3. shutdown方法會對所有不是正在執行任務的工作線程都發起中斷請求,shutdownNow方法會對所有工作線程都發起中斷請求, 不管這個工作線程是否正在執行任務。

8.2 advanceRunState方法

    private void advanceRunState(int targetState) {
        // 采用樂觀鎖的方法,來并發更改線程池狀態。
        for (;;) {
            int c = ctl.get();
            // 如果runStateAtLeast方法返回true,表示當前線程池狀態已經是目標狀態targetState
            // 采用CAS函數嘗試更改線程池狀態,如果失敗就循環繼續。
            if (runStateAtLeast(c, targetState) ||
                ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
                break;
        }
    }

這個方法來改變線程池狀態,使用樂觀鎖的方式保證并發安全。

8.3 中斷空閑狀態下的工作線程

    /**
     * 對所有不是正在執行任務的工作線程都發起中斷請求。
     */
    private void interruptIdleWorkers() {
        interruptIdleWorkers(false);
    }

    private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // 遍歷工作線程Worker集合
            for (Worker w : workers) {
                Thread t = w.thread;
                // 如果工作線程中斷標志位是false,
                // 且能夠獲取鎖,即當前工作線程沒有運行任務
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        // 發起中斷請求。
                        // 因為獲取了鎖,所以在進入中斷請求時,worker工作線程不會執行任務
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        // 釋放鎖
                        w.unlock();
                    }
                }
                // 是否只進行一個工作線程的中斷請求。
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }

遍歷工作線程Worker集合,如果工作線程出于空閑狀態,且沒有被中斷,那么就發起中斷請求。通過獨占鎖Worker知道,當前工作線程是否在執行任務。

8.4 對所有已開啟的工作線程發起中斷請求

    /**
     * 對所有工作線程都發起中斷請求, 不管這個工作線程是否正在執行任務
     */
    private void interruptWorkers() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers)
                 // 如果w的工作線程thread已經開啟,那么發起中斷請求。
                w.interruptIfStarted();
        } finally {
            mainLock.unlock();
        }
    }

遍歷工作線程Worker集合,調用Worker的interruptIfStarted方法,如果工作線程已開啟,那么就會發起中斷。

8.5 嘗試完結線程池的方法

final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            /**
             * 如果線程池是RUNNING狀態,
             * 或者線程池是TIDYING狀態(是因為已經有別的線程在終止線程池了)
             * 或者線程池是SHUTDOWN狀態且任務隊列不為空,
             * 線程池不能被terminate終止,直接return返回
             *
             */
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;
            // 線程池中工作線程數量不是0,線程池不能被terminate終止,所以要return
            if (workerCountOf(c) != 0) { // Eligible to terminate
                interruptIdleWorkers(ONLY_ONE);
                return;
            }

            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                        // 鉤子方法,提供給子類實現。表示線程池已經終止。
                        terminated();
                    } finally {
                        // 設置線程池狀態是TERMINATED
                        ctl.set(ctlOf(TERMINATED, 0));
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
        }
    }

線程池在什么情況下算是完全停止了呢?有三個條件:

  1. 線程池不是RUNNING狀態。
  2. 線程池中工作線程數量是0。
  3. 線程池中任務隊列為空。

所以在看看tryTerminate()中,前面兩個if判斷條件,就可以理解了。

8.6 等待線程池完結的方法

    public boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (;;) {
                // 如果是TERMINATED已終止狀態,那么就返回true
                if (runStateAtLeast(ctl.get(), TERMINATED))
                    return true;
                // 如果已經超時就返回false
                if (nanos <= 0)
                    return false;
                // 讓當前線程等待。并設置超時時間nanos
                nanos = termination.awaitNanos(nanos);
            }
        } finally {
            mainLock.unlock();
        }
    }

如果線程池不是TERMINATED狀態,就讓當前線程在termination條件上等待,直到線程池變成TERMINATED狀態,或者等待時間超時才會被喚醒。

8.7 工作線程退出的方法

private void processWorkerExit(Worker w, boolean completedAbruptly) {
        // 如果工作線程突然被終結,那么工作線程的數量就沒有減一。
        if (completedAbruptly)
            // 將工作線程數量減一。
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // 將工作線程的任務完成數添加到線程池完成任務總數中
            completedTaskCount += w.completedTasks;
            // 從工作線程集合中移除本工作線程
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }

        // 因為有一個工作線程已經完成被釋放,那么就去嘗試終結線程池。
        tryTerminate();

        int c = ctl.get();
        // 如果線程池狀態小于STOP,
        // 就要判斷終結了這個工作線程之后,線程池中工作線程數量是否滿足需求。
        if (runStateLessThan(c, STOP)) {
            // 如果工作線程正常終結,
            // 那么要看線程池中工作線程數量是否滿足需求。
            if (!completedAbruptly) {
                // 不允許核心池線程釋放,那么最小值是corePoolSize,否則就可以為0
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                // 但是如果任務隊列中還有任務,那么工作線程數量最少為1
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                // 如果工作線程數量小于min值,就要創建新的工作線程了。
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            // 開啟一個新的工作線程
            addWorker(null, false);
        }
    }

工作線程被釋放,有兩種情況,一種是運行完成正常結束,一種是發生異常意外終止。
當工作線程被釋放時,需要將它從工作線程集合workers中移除,將該工作線程任務完成數添加到線程池完成任務總數中。調用tryTerminate方法嘗試終結線程池。
另外因為有一個工作線程被釋放,那么就要考慮線程池中當前工作線程數量是否符合要求,要不要添加新的工作線程。

九. 創建線程池的方法。

上面分析完線程池的功能方法后,再來說說怎樣創建一個線程池。

9.1 構造函數

    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) {
        // 判斷設置的核心池數量corePoolSize、最大池數量maximumPoolSize、
        // 與線程空閑存活時間keepAliveTime的值,是否符合要求
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();

        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();

        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();

        // 賦值
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

ThreadPoolExecutor類一共有四個構造函數,前面三個構造函數都是調用后面那個構造函數來實現的。參數意義:

  1. corePoolSize: 線程池核心池線程個數。
  2. maximumPoolSize: 線程池允許最大的線程個數。
  3. keepAliveTime: 線程空閑時,允許存活的時間。
  4. unit:輔助變量,用來將keepAliveTime參數,轉成對應納秒值。
  5. workQueue:儲存所有待執行任務的阻塞隊列
  6. threadFactory:用來創建線程的工廠類
  7. handler:通過它來通知調用值,線程池拒絕了任務。
    注:有沒有注意到,沒有傳遞allowCoreThreadTimeOut這個參數,那么怎么設置這個成語變量呢?通過allowCoreThreadTimeOut(boolean value)方法來設置。

一般我們不用自己來new ThreadPoolExecutor對象,而是通過Executors這個工具類來創建ThreadPoolExecutor實例。

9.2 創建固定數量的線程池

    // 創建固定數量的線程池
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

    // 創建固定數量的線程池
    public static ExecutorService newFixedThreadPool(int nThreads, 
              ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }

根據我們前面講解,要想線程池維持固定數量的工作線程,那么工作線程就不能被釋放,就要做到兩點:

  1. allowCoreThreadTimeOut為false,這個是默認的。keepAliveTime設置為0,這樣當調用allowCoreThreadTimeOut(boolean value)方法修改allowCoreThreadTimeOut值時,會拋出異常,不允許修改。
  2. 核心池數量和最大池數量一樣,防止添加新的工作線程池。任務隊列容量要足夠大,防止任務添加到任務隊列中失敗,不能執行。

9.3 創建單個線程的線程池

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

    public static ExecutorService newSingleThreadExecutor(
              ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
    }

與固定數量的線程池相比:

  1. 將固定數量nThreads變成了1
  2. 使用了FinalizableDelegatedExecutorService這個代理類,主要作用就是當對象被銷毀時,會調用shutdown方法,停止線程池。

9.4 創建緩存線程池

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

    public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }

什么叫做緩存線程池,當有任務執行時,會創建工作線程來執行任務,當任務執行完畢后,工作線程會等待一段時間,如果還是沒有任務需要執行,那么就會釋放工作線程。

十. ThreadPoolExecutor 重要參數方法

10.1 getActiveCount 正在執行任務線程數

    public int getActiveCount() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            int n = 0;
            for (Worker w : workers)
                // isLocked() 表示這個 Worker 正在執行任務
                if (w.isLocked())
                    ++n;
            return n;
        } finally {
            mainLock.unlock();
        }
    }

這個方法獲取正在執行任務的線程數量。w.isLocked() 表示正在執行任務的 Worker

10.2 getCompletedTaskCount 返回完成任務大致數量

  public long getCompletedTaskCount() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // completedTaskCount 表示已完成 Worker 的completedTasks數量之和。
            long n = completedTaskCount;
            for (Worker w : workers)
                n += w.completedTasks;
            return n;
        } finally {
            mainLock.unlock();
        }
    }

返回已完成執行的任務的大致總數。由于任務和線程的狀態可能在計算過程中動態變化,因此返回值只是一個近似值。

completedTaskCount 表示已完成 WorkercompletedTasks 數量之和。在 Worker 退出方法 processWorkerExit() 中進行增加操作。

10.3 getTaskCount 返回已計劃執行的任務的大致總數

    public long getTaskCount() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // completedTaskCount 表示已完成 Worker 的completedTasks數量之和。
            long n = completedTaskCount;
            for (Worker w : workers) {
                n += w.completedTasks;
                // w.isLocked() 表示一個任務正在執行
                if (w.isLocked())
                    ++n;
            }
            // 再加上待執行的任務數量
            return n + workQueue.size();
        } finally {
            mainLock.unlock();
        }
    }

返回已計劃執行的任務的大致總數。由于任務和線程的狀態可能在計算過程中動態變化,因此返回值只是一個近似值。

10.4 getCorePoolSize 方法

    public int getCorePoolSize() {
        return corePoolSize;
    }

返回線程池核心線程數量。

10.5 getKeepAliveTime 方法

   public long getKeepAliveTime(TimeUnit unit) {
        return unit.convert(keepAliveTime, TimeUnit.NANOSECONDS);
    }

返回線程保持活動時間。一是當線程超過核心線程數時,超過的線程超過 keepAliveTime 時間沒有執行任務,就會關閉;二是 allowCoreThreadTimeOut 值為 true,那么核心線程空閑事件超過 keepAliveTime 也會關閉。

10.6 getLargestPoolSize 方法

    public int getLargestPoolSize() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            return largestPoolSize;
        } finally {
            mainLock.unlock();
        }
    }

獲取曾經出現的最大線程數。

10.7 getMaximumPoolSize 方法

    public int getMaximumPoolSize() {
        return maximumPoolSize;
    }

獲取最大線程數。

10.8 getPoolSize 方法

    public int getPoolSize() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // Remove rare and surprising possibility of
            // isTerminated() && getPoolSize() > 0
            return runStateAtLeast(ctl.get(), TIDYING) ? 0
                : workers.size();
        } finally {
            mainLock.unlock();
        }
    }

返回池中的當前線程數。

10.9 getQueue 方法

    public BlockingQueue<Runnable> getQueue() {
        return workQueue;
    }

返回待執行任務隊列。

十一 總結

線程池有兩個概念核心池與最大池。

  1. 核心池:線程池應該維持的工作線程數量,如果線程池中工作線程數量小于核心池數量,就會創建新的工作線程添加到線程池中。
  2. 最大池: 線程池中臨時存在的工作線程,當任務隊列不能添加新任務時,就會創建新的工作線程添加到線程池中。執行完任務后,超過一定時間沒有接受到新任務,這個臨時工作線程就會被釋放。

兩者的區別:

  1. 線程釋放:最大池中的線程當超過一定時間沒有接受到新任務,就會被釋放,而核心池中的線程,一般不釋放,只有設置allowCoreThreadTimeOut為true,且超過一定時間沒有接受到新任務,也會被釋放。
  2. 創建時機:線程池中工作線程數量小于核心池數量,就會創建核心池線程。但是對于最大池來說,只有任務隊列已滿,不能添加新任務時,才會創建新線程,放入最大池中。
    注:一般稱小于等于corePoolSize數量的工作線程池是核心池中的線程,大于corePoolSize數量的工作線程池就是最大池中的線程。

11.1 線程池執行任務流程

通過execute方法執行新任務command,分為三個步驟:

  1. 線程池中工作線程數量小于核心池數量,那么就開啟新的工作線程來執行任務。
  2. 線程池中工作線程數量達到核心池數量,那么就將新任務添加到任務隊列中。
  3. 如果新任務添加到任務隊列失敗,那么就開啟新的工作線程來執行任務(這個線程就在最大池中了)。

在每個工作線程,會通過循環,調用getTask方法,不斷地從任務隊列中獲取任務來執行。如果任務隊列中沒有任務,那么getTask方法會阻塞當前工作線程。

但是工作線程被喚醒后,getTask方法返回null,那么就會跳出循環,該工作線程運行結束,準備釋放。

11.2 終止線程池

線程池不可能立即就終止,因為涉及到線程池正在執行任務的線程和任務隊列中等待執行的任務該如何處理問題,有兩個方式:

  1. shutdown方法:不能再向線程池中添加新任務了,但是已經添加到任務隊列的任務還是會執行,也不會對正在執行任務的線程發起中斷請求。等待任務隊列任務執行完成,釋放線程池中所有線程,線程池進入完全終止狀態。
  2. shutdownNow方法:不能再向線程池中添加新任務了,也不會執行已經添加到任務隊列的任務,但是會返回未執行的任務集合。而且對所有工作線程都發起中斷請求, 不管這個工作線程是否正在執行任務。等待線程池中所有線程釋放,線程池進入完全終止狀態。

兩者的區別:

兩者都不能再向線程池中添加新任務了。shutdown方法還是會將已添加的任務都執行完畢,而shutdownNow方法不會再執行任何新任務了。
注:對于正在執行的任務是可能執行完成的,因為中斷請求只能中斷處于WAITING與TIMED_WAITING狀態的線程,對于處于其他狀態的線程不起作用。

十二. 重要示例

12.1 正常運行線程池

package com.zhang._22._5;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

class Run implements Runnable {

    private int index;

    public Run(int index) {
        this.index = index+1;
    }

    @Override
    public void run() {
        System.out.println("--"+Thread.currentThread().getName()+"開始運行 任務"+index);
        try {
            int waitTime = 100 + index * 10;
            Thread.sleep(waitTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("======="+Thread.currentThread().getName()+"結束 任務"+index);
    }
}

class MyThreadFactory implements ThreadFactory {
    private int sequenceNumber = 0;

    @Override
    public Thread newThread(Runnable r) {
        return new Thread(r, "線程"+(++sequenceNumber));
    }
}

public class ThreadPoolExecutorTest {

    public static void main(String[] args) {

        ThreadFactory threadFactory = new MyThreadFactory();

        // 固定數量的線程池
        ExecutorService service = Executors.newFixedThreadPool(3, threadFactory);

//        // 單個線程的線程池
//        ExecutorService service = Executors.newSingleThreadExecutor(threadFactory);
//
//        // 緩存線程池
//        ExecutorService service = Executors.newCachedThreadPool(threadFactory);

        for (int i = 0; i < 6; i++) {
            service.execute(new Run(i));
        }
    }
}

運行結果:

--線程1開始運行 任務1
--線程2開始運行 任務2
--線程3開始運行 任務3
=======線程1結束 任務1
--線程1開始運行 任務4
=======線程2結束 任務2
--線程2開始運行 任務5
=======線程3結束 任務3
--線程3開始運行 任務6
=======線程1結束 任務4
=======線程2結束 任務5
=======線程3結束 任務6

這里使用的是固定數量的線程池,所以只有三個線程來執行任務,未執行到的任務只能等待。
如果換成單個線程的線程池,那么只有一個線程在執行任務。
而緩存線程池呢?你就會發現居然有六個線程在執行任務,就是有多少任務創建多少個線程。

運行完任務后,你會發現程序沒有結束,那是因為線程池沒有被終止。

12.2 終止線程池

package com.zhang._22._5;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

class Run implements Runnable {

    private int index;

    public Run(int index) {
        this.index = index+1;
    }

    @Override
    public void run() {
        System.out.println("--"+Thread.currentThread().getName()+"開始運行 任務"+index);
        try {
            int waitTime = 100 + index * 10;
            Thread.sleep(waitTime);
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName()+" 發生中斷異常  exception=="+e.getMessage());
        }
        System.out.println("======="+Thread.currentThread().getName()+"結束 任務"+index);
    }
}

class MyThreadFactory implements ThreadFactory {
    private int sequenceNumber = 0;

    @Override
    public Thread newThread(Runnable r) {
        return new Thread(r, "線程"+(++sequenceNumber));
    }
}

public class ThreadPoolExecutorTest {

    public static void main(String[] args) {

        ThreadFactory threadFactory = new MyThreadFactory();

        // 固定數量的線程池
        ExecutorService service = Executors.newFixedThreadPool(3, threadFactory);

//        // 單個線程的線程池
//        ExecutorService service = Executors.newSingleThreadExecutor(threadFactory);
//
//        // 緩存線程池
//        ExecutorService service = Executors.newCachedThreadPool(threadFactory);

        for (int i = 0; i < 6; i++) {
            service.execute(new Run(i));
        }
        // 還是會執行完已經添加的任務
        service.shutdown();
    }
}

運行結果:

--線程1開始運行 任務1
--線程3開始運行 任務3
--線程2開始運行 任務2
=======線程1結束 任務1
--線程1開始運行 任務4
=======線程2結束 任務2
--線程2開始運行 任務5
=======線程3結束 任務3
--線程3開始運行 任務6
=======線程1結束 任務4
=======線程2結束 任務5
=======線程3結束 任務6

Process finished with exit code 0

使用shutdown方法,還是會執行完已經添加的任務。最后程序退出。

package com.zhang._22._5;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

class Run implements Runnable {

    private int index;

    public Run(int index) {
        this.index = index+1;
    }

    @Override
    public void run() {
        System.out.println("--"+Thread.currentThread().getName()+"開始運行 任務"+index);
        try {
            int waitTime = 100 + index * 10;
            Thread.sleep(waitTime);
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName()+" 發生中斷異常  exception=="+e.getMessage());
        }
        System.out.println("======="+Thread.currentThread().getName()+"結束 任務"+index);
    }
}

class MyThreadFactory implements ThreadFactory {
    private int sequenceNumber = 0;

    @Override
    public Thread newThread(Runnable r) {
        return new Thread(r, "線程"+(++sequenceNumber));
    }
}

public class ThreadPoolExecutorTest {

    public static void main(String[] args) {

        ThreadFactory threadFactory = new MyThreadFactory();

        // 固定數量的線程池
        ExecutorService service = Executors.newFixedThreadPool(3, threadFactory);

//        // 單個線程的線程池
//        ExecutorService service = Executors.newSingleThreadExecutor(threadFactory);
//
//        // 緩存線程池
//        ExecutorService service = Executors.newCachedThreadPool(threadFactory);

        for (int i = 0; i < 6; i++) {
            service.execute(new Run(i));
        }

        service.shutdownNow();
    }
}

運行結果:

--線程1開始運行 任務1
--線程2開始運行 任務2
--線程3開始運行 任務3
線程2 發生中斷異常  exception==sleep interrupted
線程1 發生中斷異常  exception==sleep interrupted
=======線程1結束 任務1
=======線程2結束 任務2
線程3 發生中斷異常  exception==sleep interrupted
=======線程3結束 任務3

Process finished with exit code 0

使用shutdownNow方法,在任務隊列中等待的任務是不會執行的,而且立即發起線程中斷請求。

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

推薦閱讀更多精彩內容