死磕Java多線程之線程池的基本使用與核心原理

Java多線程之線程池

一、線程池的自我介紹

1. 線程池的重要性(爲什麼使用線程池)

線程池可以應對突然大爆發量的訪問,通過有限個固定線程爲大量的操作服務,減少創建和銷燬線程所需的時間。

  • 降低資源消耗:通過重複利用已創建的線程降低線程創建和銷燬造成的消耗。
  • 提高響應:當任務到達時,任務可以不需要等到線程創建就能立即執行。
  • 提高線程的可管理性:線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。
2. 線程池的使用場景

(1) ***高併發、任務執行時間短***的業務,線程池線程數可以設置爲CPU核數+1,減少線程上下文的切換。但是當處理線程數設置極大的時候和非線程池模型幾乎沒有差別
(2) ***併發不高、任務執行時間長***的業務要區分來看:

  • 假如是業務時間長集中在IO操作上,也就是***IO密集型的任務***,因爲IO操作並不佔用CPU,所以不要讓所有的CPU閒下來,可以加大線程池中的線程數目,讓CPU處理更多的業務。

  • 假如是業務時間長集中在計算操作上,也就是***計算密集型任務***,這個就沒辦法了,和一樣吧,線程池中的線程數設置得少一些,減少線程上下文的切換。

    (3) 併發高、業務執行時間長,解決這種類型任務的關鍵不在於線程池而在於整體架構的設計,看看這些業務裏面某些數據是否能做緩存是第一步,增加服務器是第二步,至於線程池的設置,設置參考第二小步。最後,業務執行時間長的問題,也可能需要分析一下,看看能不能使用中間件對任務進行拆分和解耦。

二、創建和停止線程

1. 線程池構造函數
參數名 類型 含義
corePoolSize int 核心線程數
maxPoolSize int 最大線程數
keepAliveTime long 保持存活的時間
workQueue BlockingQueue 任務存儲隊列
threadFactory ThreadFactory 當線程池需要使用新的線程是,會使用threadFactory來創建新的線程
Handler RejectedExecutionHandler 由於線程池無法接受你鎖提交的任務的拒絕策略

詳細介紹:

  • corePoolSize:線程池核心線程數量,核心線程不會被回收,即使沒有任務執行,也會保持空閒狀態。如果線程池中的線程少於此數目,則在執行任務時創建。
  • maximumPoolSizemaxPoolSize:池允許最大的線程數,當線程數量達到corePoolSize,且workQueue隊列塞滿任務了之後,繼續創建線程。
  • keepAliveTime:超過corePoolSize之後的“臨時線程”的存活時間。
  • unitkeepAliveTime的單位。
  • workQueue:當前線程數超過corePoolSize時,新的任務會處在等待狀態,並存在workQueue中,BlockingQueue是一個先進先出的阻塞式隊列實現,底層實現會涉及Java併發的AQS機制。
  • threadFactory:創建線程的工廠類,通常我們會自頂一個threadFactory設置線程的名稱,這樣我們就可以知道線程是由哪個工廠類創建的,可以快速定位。
  • handler:線程池執行拒絕策略,當線數量達到maximumPoolSize大小,並且workQueue也已經塞滿了任務的情況下,線程池會調用handler拒絕策略來處理請求。
2. 線程池的創建

線程添加規則:

舉個例子:線程池核心池大小爲5, 最大池大小爲10,隊列爲100。

那麼線程添加的規則是:線程請求最多會創建5個,然後任務將會被添加到隊列中,知道達到100, 當隊列已滿,將會創建新的線程,知道線程最大10,如果再來任務,就拒絕。


線程池應該手動創建,這樣可以讓我們更加明確線程池的運行規則,避免資源耗盡的風險。接下來看一下jdk爲我們提供的幾種線程池:

Executors.newFixedThreadPool:定長線程池

  • 簡介:創建可容納固定數量線程的池子,每隔線程的存活時間是無限的,當池子滿了就不在添加線程了;如果池中的所有線程均在繁忙狀態,對於新任務會進入阻塞隊列中(無界的阻塞隊列)

  • 適用: 執行長期的任務,性能好很多。

  • 構造函數:

    corePoolSizemaximumPoolSizenThreads

    keepAliveTime:0L(不限時:存活時間無限)

    unit: TimeUnit.MILLISECONDS

    workQueueLinkedBlockingQueue該無界隊列容量是Integer.MAX_VALUE,吞吐量通常要高於ArrayBlockingQueue。由於沒有上限,當任務越來越多,並且無法及時處理完畢,也就是請求堆積越來越多,會容易造成佔用大量的內存,會導致OOM

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    
  • 實例:

    public class ThreadTest {
        public static void main(String[] args) {
            ExecutorService service = Executors.newFixedThreadPool(4);
            for (int i = 0; i < 100; i++) {
                service.execute(new Task());
            }
        }
    }
    
    class Task implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " => 正在運行...");
        }
    }
    

Executors.newSingleThreadExecutor:單線程化的線程池

  • 簡介:創建只有一個線程的線程池,且線程的存活時間是無限的;當該線程正繁忙時,對於新任務會進入阻塞隊列中(無界的阻塞隊列)

  • 適用:一個任務一個任務執行的場景

  • 構造函數:

    corePoolSizemaximumPoolSize: 1

    keepAliveTime0L(不限時:存活時間無限)

    unitTimeUnit.MILLISECONDS

    workQueueLinkedBlockingQueue該無界隊列容量是Integer.MAX_VALUE,吞吐量通常要高於ArrayBlockingQueue。由於沒有上限,當任務越來越多,並且無法及時處理完畢,也就是請求堆積越來越多,會容易造成佔用大量的內存,會導致OOM

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

    public class ThreadPoolTest {
        public static void main(String[] args) {
            ExecutorService service = Executors.newSingleThreadExecutor();
            for (int i = 0; i < 100; i++) {
                service.execute(new SingleTask());
            }
        }
    }
    class SingleTask implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " => 正在運行...");
        }
    }
    

Excutors.newCachedThreadPool:無界線程池,可以回收多餘的線程。

  • 簡介:當有新任務到來,則插入到SynchronousQueue中,由於SynchronousQueue是同步隊列,因此會在池中尋找可用線程來執行,若有可以線程則執行,若沒有可用線程則創建一個線程來執行該任務;若池中線程空閒時間超過指定大小,則該線程會被銷燬。

  • 適用:執行很多短期異步的小程序或者負載較輕的服務

  • 構造函數

    corePoolSize:0

    maximumPoolSizeInteger.MAX_VALUE最大,可能會創建數量非常多的線程,甚至導致OOM

    keepAliveTime60L(不限時:存活時間無限)

    unitTimeUnit.SECONDS(秒)

    workQueueSynchronousQueue直接交換隊列,容量爲0,任務來了直接交給線程池,所有會創建很多現線程,60S之後會收回。

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

    public class ThreadPoolCacheTest {
        public static void main(String[] args) {
            ExecutorService service = Executors.newCachedThreadPool();
            for (int i = 0; i < 1000; i++) {
                service.execute(new CacheTask());
            }
        }
    }
    class CacheTask implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " => 正在運行...");
        }
    }
    

Excutors.newScheduledThreadPool

  • 簡介:創建一個固定大小的線程池,線程池內線程存活時間無限制,線程池可以支持定時及週期性任務執行,如果所有線程均處於繁忙狀態,對於新任務會進入DelayedWorkQueue隊列中,這是一種按照超時時間排序的隊列結構。

  • 適用:週期性執行任務的場景

  • 構造函數:

    public ScheduledThreadPoolExecutor(int corePoolSize) {
            super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
                  new DelayedWorkQueue());
        }
    
  • 實例:

    public class ThreadPoolScheduledTest {
        public static void main(String[] args) {
            ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
            // 五秒之後執行
            service.schedule(new SingleTask(), 5, TimeUnit.SECONDS);
            // 五秒之後執行,之後每五秒執行一次
            service.scheduleAtFixedRate(new ScheduledTask(), 5, 5, TimeUnit.SECONDS);
            for (int i = 0; i < 1000; i++) {
                service.execute(new CacheTask());
            }
        }
    }
    class ScheduledTask implements Runnable {
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " => 正在運行...");
        }
    }
    
3. 合理設置線程池中線程數量
  • CPU密集型(加密、計算hash等):最佳線程數爲CPU核心數的1-2倍左右
  • 耗時IO型(讀寫數據庫、文件、網絡讀寫等):最佳線程數一般會大於CPU核心數很多倍,以JVM線程監控顯示繁忙情況爲依據,保證線程空閒可以銜接上
  • 線程數 = CPU核心數 * (1 + 平均等待時間 / 平均工作時間)
4. 線程池的停止

五個停止線程池的相關方法:

  • shutdown:線程池拒接收新提交的任務,同時等待線程池裏的任務執行完畢後關閉線程池
  • shutdownNow:線程池拒接收新提交的任務,同時立馬關閉線程池,線程池裏的任務不再執行
  • isShutdown:返回Boolean,標識線程池是否進入停止的狀態
  • isTerminated:返回Boolean,線程池是否完全停止。
  • awaitTermination:測試一段時間內線程池是否停止。

三、線程池拒絕策略

3.1. 線程池拒絕時機:
  • Executor關閉時,提交新任務會被拒絕
  • 以及當Executor對最大線程和工作容量使用有限邊界並且已經飽和時。
3.2. 拒絕策略
  • AbortPolicy:直接拋出RejectedExecutionException異常,也不執行這個任務了
  • DiscardPolicy:會讓被線程池拒絕的任務直接拋棄,不會拋異常也不會執行。
  • DiscardOldestPolicy:當任務唄拒絕添加時,會拋棄任務隊列中最舊的任務也就是最先加入隊列的,再把這個新任務添加進去。
  • CallerRunsPolicy:任務被拒絕添加後,會調用當前線程池的所在的線程去執行被拒絕的任務。

四、對線程池的裝飾

我們可以繼承ThreadPoolExecutor這個類,來實現我們的一些其他的操作,例如在線程日誌、統計等線程池不具備的功能。也可以看ThreadPoolExecutor這個類源碼,註釋中就有。還是多看源碼啊,哈哈!

public class TestPools extends ThreadPoolExecutor {

    // 暫停的標誌
    private boolean isPaused;

    // 鎖
    private ReentrantLock lock = new ReentrantLock();

    private Condition unPaused = lock.newCondition();

    // 構造函數
    public TestPools(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }
	
    // 可以再線程執行任務之前做一些處理
    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
        lock.lock();
        try {
            while(isPaused) {
                unPaused.await();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    /**
     * 喚醒,可將暫停的線程池喚醒
     */
    public void resume() {
        lock.lock();
        try {
            isPaused = false;
            unPaused.signalAll();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 暫停,將線程池暫停
     */
    private void pause() {
        lock.lock();
        try {
            isPaused = true;
        } finally {
            lock.unlock();
        }
    }
	
    // 測試代碼
    public static void main(String[] args) throws InterruptedException {
        TestPools pools = new TestPools(20, 20, 10L, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
        for (int i = 0; i < 1000; i++) {
            pools.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + " => 正在執行...");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        Thread.sleep(1500);
        pools.pause();
        System.out.println("線程池暫停");
        Thread.sleep(1500);
        System.out.println("線程池喚醒");
        pools.resume();
        pools.shutdown();
    }
}

五、線程池實現原理簡單分析

5.1. 線程池的組成部分:
  • 線程池管理器
  • 工作線程
  • 任務隊列
  • 任務接口
5.2. 工作流程

當一個任務提交至線程池之後:

1、線程池首先判斷***核心線程池***裏面的線程是否已經滿了,如果沒有滿,則創建一個新的工作線程來執行線程,否則到第2步;

2、判斷***工作隊列***是否已經滿了,如果沒有滿,將線程放入工作隊列,否則到第3步;

3、判斷***線程池***裏最大線程數是否已經滿了,沒有滿則創建一個新的工作線程來執行,否則交給拒絕策略來處理任務。

5.2. 線程池的內部狀態控制變量
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;

AtomicInteger變量ctl(1110 0000 0000 0000 0000 0000 0000 0000)中低29位做線程數,高3位做線程池狀態。

5.3. 線程池狀態轉換:

5.4. 任務執行流程跟蹤:

先弄一個測試代碼:

public class TestPoolOne {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 10, 10L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(4), Executors.defaultThreadFactory());
        for (int i = 0; i < 10; i++) {
            pool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(Thread.currentThread().getName() + " - 正在執行...");
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

我們將新任務通過execute(Runnable command)方法進行提交

execute執行流程:

ctl初始值:1110 0000 0000 0000 0000 0000 0000 0000,低29位是0,所有線程池容量初始值0,capacity:0001 1111 1111 1111 1111 1111 1111 1111, workerCountOf計算線程池現在線程的數量,初始的時候是0。

/**
 * 在將來的某個時間執行給定的任務。 任務可以在新線程或現有池線程中執行。
 *
 * 如果由於該執行程序已關閉或已達到其容量而無法提交執行任務,
 * 則該任務由當前的拒絕策略處理。
 *
 * @param command 要執行的任務
 * @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();
    /*
     * 進行3個步驟:
     * 1. 如果正在運行的線程少於corePoolSize線程,請嘗試使用給定命令作爲其
     * 第一個任務來啓動新線程。對addWorker的調用原子地檢查runState和workerCount,
     * 從而通過返回false來防止在不應該添加線程的情況下發出錯誤警報。
     * 2. 如果任務可以成功排隊,那麼我們仍然需要仔細檢查是否應該添加線程
     * (因爲現有線程自上次檢查後就死掉了)或自進入此方法以來該池已關閉。
     * 因此,我們重新檢查狀態,並在必要時回滾排隊,如果已停止,或者在沒有線程的情況下啓動新線程。
     * 3. 如果我們無法將任務排隊,則嘗試添加一個新線程。如果失敗,
     * 我們知道我們已關閉或已飽和,因此拒絕該任務。
     */
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    else if (!addWorker(command, false))
        reject(command);
}

addWorker方法,主要負責創建新的線程並執行任務,線程池創建執行任務時,需要獲取全局鎖:

private boolean addWorker(Runnable firstTask, boolean core) {
    /* 當線程池處於 RUNNING (運行)狀態時,只有在線程池中的有效線程數
     * 被成功加一以後,纔會退出該循環而去執行後邊的代碼。
     * 也就是說當線程池在 RUNNING (運行)狀態下退出該 retry 循環時, 線程池中的有效線程數
     * 一定少於此次設定的最大線程數(可能是 corePoolSize 或 maximumPoolSize)。
     * 
     * retry 是個爲了跳出多層循環. 
     * break retry; 跳出循環之後不會再次進入循環
     * continue retry; 會跳出內層循環,從外層循環再次開始
     */
    retry:
    // for 和 while 之間啥區別,C語言早之前while會多調用幾條彙編指令,現在Java優化已經一樣了
    for (;;) {
        int c = ctl.get();
        // 獲取當前線程池狀態
        int rs = runStateOf(c);

        /* 表示沒有創建新線程, 新提交的任務也沒有被執行.
         * 線程池滿足如下條件中的任意一種時, 就會直接結束該方法, 並且返回 false
         * (1) 處於STOP/TIDYING/TERMINATED轉態
         * (2) 處於SHUTDOWN狀態並且參數fistTask不爲null
         * (3) 處於SHUTDOWN狀態並且阻塞隊列workQueue爲空
         */
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        for (;;) {
            // 計算線程池個數
            int wc = workerCountOf(c);
            /* 如果線程池內的有效線程數大於或等於了理論上的最大容量 CAPACITY 或者實際
             * 設定的最大容量, 就返回 false, 直接結束該方法. 這樣同樣沒有創建新線程, 
             * 新提交的任務也同樣未被執行。
             * core => true 最大容量爲corePoolSize,否則爲maximumPoolSize
             */
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // 有效線程數加一
            if (compareAndIncrementWorkerCount(c))
                // 跳出多層循環,執行後面的代碼
                break retry;
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }

    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        // 根據參數 firstTask來創建 Worker對象 w。具體看一下下面的Worker中的分析
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                int rs = runStateOf(ctl.get());

                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                // 將線程啓動,看Worker下的run方法
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

接着看一下Worker線程池中的內部類

構造函數

Worker(Runnable firstTask) {
    // 禁止中斷,直到runWorker
    setState(-1); 
    this.firstTask = firstTask;
    // 創建新線程
    this.thread = getThreadFactory().newThread(this);
}

運行線程

/* Worker運行循環。反覆從隊列中獲取任務並執行它們,同時解決許多問題:
 *
 * 1. 我們可以從初始任務開始,在這種情況下,我們不需要獲取第一個任務。
 * 否則,只要池正在運行,我們就會從getTask獲取任務。
 * 如果返回null,則工作程序將由於更改的池狀態或配置參數而退出。
 * 其他退出是由於外部代碼中的異常引發而導致的,在這種情況下completedAbruptly成立,
 * 這通常導致processWorkerExit替換此線程。
 *
 * 2. 在運行任何任務之前,先獲取鎖,以防止任務執行時其他池中斷,
 * 然後確保除非池正在停止,否則此線程不會設置其中斷。
 *
 * 3. 每個任務運行之前都會調用beforeExecute,
 * 這可能會引發異常,在這種情況下,我們將導致線程死掉
 * (中斷,帶有completelyAbruptly true的循環),而不處理該任務。
 *
 * 4. 假設beforeExecute正常完成,我們運行任務,收集其引發的任何異常以發送給afterExecute。
 * 我們分別處理RuntimeException,Error(規範保證我們可以捕獲它們)和任意Throwables。
 * 因爲我們無法在Throwable.run中拋出Throwable,所以我們將它們包裝在Errors中
 * (輸出到線程的UncaughtExceptionHandler)。任何拋出的異常也會保守地導致線程死亡。
 *
 * 5. task.run完成後,我們調用afterExecute,這可能還會引發異常,這也會導致線程死亡。
 * 根據JLS Sec 14.20,此異常是即使task.run拋出也會生效的異常。
 *
 * 異常機制的最終結果是afterExecute和線程的
 * UncaughtExceptionHandler具有與我們所能提供的有關用戶代碼遇到的任何問題一樣準確的信息。
 */
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        while (task != null || (task = getTask()) != null) {
            w.lock();
            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

獲取阻塞隊列中的任務:

/* 根據當前配置設置執行阻塞或定時等待任務,或者如果此工作程序由於以下任何原因而必須退出,則返回null
 *
 * 1. 
 *
 * 2. 線程池停止
 *
 * 3. 線程池關閉和隊列爲空
 *
 * 4. 該工作程序超時等待任務,並且在定時等待之前和之後均會終止工作
 * (即{@code allowCoreThreadTimeOut || workerCount> corePoolSize})
 * 並且如果隊列爲非空,此工作程序不是池中的最後一個線程。
 */
private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?

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

        // 如果線程池已停止, 或者線程池被關閉並且線程池內的阻塞隊列爲空, 則結束該方法並返回 null
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

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

        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
            workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

六、線程池狀態和注意

6.1. 線程池的狀態
  • running:排隊任務
  • shutdown:不接受新任務,但處理排隊任務
  • stop:不接受新任務,也不處理排隊任務,並中斷正在進行的任務
  • tidying:所有任務都已經終止,workerCount爲零時,線程會轉換到tidying狀態,並將運行terminate()鉤子方法
  • terminatedterminate()運行完成。
6.2. 使用線程池需要注意:
  • 避免任務堆積
  • 避免線程數過度增加
  • 排查線程泄露(業務邏輯問題,線程無法結束)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章