線程池類ThreadPoolExecutor解析

1、使用線程池的好處

線程使應用能夠更加充分合理地協調利用CPU、內存、網絡、I/O等系統資源。線程的創建需要開闢虛擬機棧、程序計數器、本地方法棧等線程私有的內存空間。在線程銷燬時需要回收這些資源。頻繁地創建和銷燬線程會浪費大量的系統資源,增加併發編程風險。另外,在服務器負載過大的時候,如何讓新線程等待或者友好地拒絕服務?這都是線程自身無法解決的。所以需要通過線程池協調多個線程,並實現類似主次線程隔離、定時執行、週期執行等任務。線程池的作用包括:

  • 利用線程池管理並複用線程、控制最大併發數。
  • 實現任務線程隊列緩存策略和拒絕機制。
  • 實現某些與實踐相關的功能,如定時執行、週期執行等。
  • 隔離線程環境。比如,交易服務和搜索服務在同一臺服務器上,分別開啓兩個線程池,交易線程的資源消耗明顯要大;因此,通過配置獨立的線程池,將較慢的交易服務與搜索服務隔離開,避免各服務線程相互影響。

2、ThreadPoolExecutor

瞭解線程池的基本作用後,我們學習一下線程池是如何創建線程的。首先從ThreadPoolExecutor構造方法講起,學習如何自定義ThreadFactory和RejectedExecutionHandler,並編寫一個最簡單的線程池示例。然後通過分析ThreadPoolExecutor的execute和addWorker兩個核心方法,學習如何把任務線程加入到線程池中運行。

ThreadPoolExecutor構造方法如下:

public ThreadPoolExecutor(
        int corePoolSize,                          //參數1
        int maximumPoolSize,                       //參數2
        long keepAliveTime,                       //參數3
        TimeUnit unit,                            //參數4
        BlockingQueue<Runnable> workQueue,        //參數5
        ThreadFactory threadFactory,              //參數6
        RejectedExecutionHandler handler) {       //參數7
        if (corePoolSize < 0 ||
                //maximumPoolSize必須大於或等於1,也要大於或等於corePoolSize(第一處)
                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;
    }
  1. 參數1,corePoolSize:表示常駐核心線程數。如果等於0,則任務執行完成以後,沒有任何請求進入時銷燬線程池的線程;如果大於0,即使本地任務執行完畢,核心線程也不會被銷燬。這個值的設置非常關鍵,設置過大會浪費資源,設置過小會導致線程頻繁地創建或銷燬。
  2. 參數2,maximumPoolSize:表示線程池能夠容納同時執行的最大線程數。從上方示例代碼中的第一處來看,必須大於或等於1.如果待執行的線程數大於此值,需要藉助第五個參數的幫助,緩存在隊列中。如果maximumPoolSize與corePoolSize相等,即是固定大小線程池。
  3. 參數3,keepAliveTime:表示線程池中線程空閒時間,當空閒時間達到keepAliveTime值時,線程會被銷燬,直到只剩下corePoolSize個線程爲止,避免浪費內存和句柄資源。在默認情況下,當線程池的線程數大於corePoolSize時,keepAliveTime纔會起作用。但是當ThreadPoolExecutor的allowCoreThreadTimeOut變量設置爲true時,核心線程超時後也會被回收
  4. 參數4,TimeUnit:表示時間單位。KeepAliveTime的時間單位通常是TimeUnit.SECONDS。
  5. 參數5,workQueue:表示緩存隊列。當請求的線程數大於corePoolSize時,線程進入BlockingQueue阻塞隊列(請注意,是當corePoolSize不夠用時,將任務加入緩存隊列,當緩存隊列也容納不下任務時,再開闢新的線程來處理任務,直到線程數到達maximumPoolSize)。後續示例代碼中使用的LinkedBlockingQueue是單向鏈表,使用鎖來控制入隊和出隊的原子性,兩個鎖分別控制元素的添加和獲取,是一個生產消費模型隊列。
  6. 參數6,threadFactory表示線程工廠。它用來生產一組相同任務的線程。線程池的命名是通過給這個factory增加組名前綴來實現的。在虛擬機棧分析時,就可以知道線程任務是由哪個線程工廠產生的。
  7. 參數7,handler表示執行拒絕策略的對象。當第五個參數workQueue的任務緩存區到達上限後,並且活動線程數等於maximumPoolSize的時候,線程池通過該策略處理請求,這是一種簡單的限流保護。友好地拒絕策略可以是如下三種:保存到數據庫進行削峯填谷,在空閒時再提取出來執行、轉向某個提示頁面、打印日誌

從代碼中的第二處來看,隊列、線程工廠、拒絕處理服務都必須有示例對象,但在實際編程中,很少有程序員對這三者進行實例化,而是通過Executors這個線程池靜態工廠提供默認實現,那麼Executors與ThreadPoolExecutor是什麼關係呢?(從源碼上看,ThreadPoolExecutor是Executors的底層實現)線程池相關類圖如下所示:

ExecutorService接口繼承了Executor接口,定義了管理線程任務的方法。ExecutorService的抽象類AbstractExecutorService提供了submit()、invokeAll()等部分方法的實現。但是核心方法Executor.execute()並沒有在這裏實現。因爲所有的任務都在這個方法裏執行,不同實現會帶來不同的執行策略。通過Executor的靜態工廠方法可以創建三個線程池的包裝對象:ForkJoinPool、ThreadPoolExecutor、ScheduledThreadPoolExecutor。

Executors(Java提供用來創建線程池的類,在JUC包下)的核心方法有五個(這五個方法每個都有多種重載方法,我只選取了一種):

  1. Executor.newWorkStealingPool:JDK8引入,創建持有足夠線程的線程池支持給定的並行度,並通過使用多個隊列減少競爭,此構造方法中把CPU數量設置爲默認的並行度。
         public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>(),
                                          threadFactory);
        }

     

  2. Executors.newCachedThreadPool:maximumPoolSize最大可以至Integer.MAX_VALUE,是高度可伸縮的線程池,如果達到這個上限,相信沒有任何服務器能夠繼續工作,肯定會拋出OOM異常。keepAliveTime默認60秒,工作線程處於空閒狀態,則回收工作線程。如果任務數增加,再次創建出新線程處理任務。
        public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                          60L, TimeUnit.SECONDS,
                                          new SynchronousQueue<Runnable>(),
                                          threadFactory);
        }

     

  3. Executors.newScheduledThreadPool:線程數最大值Integer.MAX_VALUE,與上述相同,存在OOM風險。它是ScheduledExecutorService接口家族的實現類,支持定時及週期性任務執行。相比Timer,ScheduledExecutorService更安全,功能更加強大,與newCachedThreadPool的區別是不回收工作線程。
         public static ScheduledExecutorService newScheduledThreadPool(
                int corePoolSize, ThreadFactory threadFactory) {
            return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
        }

     

  4. Executors.newSingleThreadExecutor:創建一個單線程的線程池,相當於單線程串行執行所有任務,保證按任務的提交順序依次執行。
        public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
            return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1, 1,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>(),
                                        threadFactory));
        }

     

  5. Executors.newFixedThreadPool:輸入的參數即是固定線程數,即是核心線程數也是最大線程數,不存在空閒線程,所以keepAliveTime等於0:
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    
    //這裏輸入的隊列沒有指明長度,看一下LinkedBlockingQueue的構造方法
    public LinkedBlockingQueue(){
        this(Integer.MAX_VALUE);
    }
    //使用這樣的無界隊列,如果瞬間請求量很大的話,會有OOM風險

     

除了newWorkStealingPool外,其他四個創建方式都存在資源耗盡的風險。

Executors中默認的線程工廠(threadFactory)和拒絕策略(handler)過於簡單,通常對用戶來說都不太友好,線程工廠需要做創建前的準備工作,對線程池創建的線程必須明確標識,就像藥品的生產批號一樣,爲線程本身指定有意義的名稱和相應的序列號。拒絕策略應該考慮到業務場景,返回相應的提示或者友好地跳轉,以下爲簡單的ThreadFactory示例:

package Test;

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

public class UserThreadFactory implements ThreadFactory {
    
    //name prefix譯爲名稱前綴
    private final String namePrefix;
    
    //AtomicInteger是一個原子操作類,能夠保證操作的原子性
    private final AtomicInteger nextId = new AtomicInteger();

    //定義線程組名稱whatFeatureOfGroup,在使用jstack來排查線程問題時,非常有幫助
    UserThreadFactory(String whatFeatureOfGroup){
        this.namePrefix = "UserThreadFactory's" + whatFeatureOfGroup + "-Worker-";
    }

    @Override
    public Thread newThread(Runnable task) {
        String name = namePrefix + nextId.getAndIncrement();
        Thread thread = new Thread(null, task, name, 0, false);
        System.out.println(thread.getName());
        return thread;
    }
}

//任務執行體
class Task implements Runnable{

    private final AtomicLong count = new AtomicLong(0L);

    @Override
    public void run() {
        System.out.println("running_" + count.getAndIncrement());
    }
}

這個示例包括線程工廠和任務執行體的定義,通過newThread方法快速、統一地創建線程任務,強調線程一定要有特定意義的名稱,方便出錯時回溯。

簡單地實現一下RejectedExecutionHandler,實現了接口的rejectedExecution方法,打印出當前線程池狀態:

public class UserRejectHandler implements RejectedExecutionHandler{
    @Override
    public void rejectedExecution(Runnable task, ThreadPoolExecutor executor){
        System.out.println("task rejected." + executor.toString());
    }
}

當一個任務Task欲通過threadPoolexecutor.execute(Runnable)添加到線程中時,會出現以下幾種情況:

  1. 此時線程池中的線程數小於corePoolSize(可以通過設置參數讓核心線程也空閒超時銷燬)時,即使線程池中的線程都處於空閒狀態,那麼也要創建一個新的線程來執行這個任務Task;
  2. 此時線程池中線程數量等於corePoolSize,但是緩衝隊列workQueue未滿,則將任務加入緩存隊列等待處理;
  3. 此時線程池中線程數量大於corePoolSize,緩衝隊列workQueue已滿,但是線程數量小於maximumPoolSize時,則新建一個線程來執行此任務Task;
  4. 此時線程池中線程數量大於corePoolSize,緩衝隊列workQueue已滿,且線程數量等於maximumPoolSize時,那麼通過指定的handler來對該任務Task執行拒絕策略。

handler有四個選擇,在ThreadPoolExecutor中提供了四個公開的靜態內部類:

  • AbortPolicy(默認):丟棄任務並拋出RejectedExecutionException異常。
        public static class AbortPolicy implements RejectedExecutionHandler {
            /**
             * Creates an {@code AbortPolicy}.
             */
            public AbortPolicy() { }
    
            /**
             * Always throws RejectedExecutionException.
             *
             * @param r the runnable task requested to be executed
             * @param e the executor attempting to execute this task
             * @throws RejectedExecutionException always.
             */
            public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
                throw new RejectedExecutionException("Task " + r.toString() +
                                                     " rejected from " +
                                                     e.toString());
            }
        }

     

  • DiscardPolicy:丟棄任務,但不拋出異常,這是不推薦的做法。
        public static class DiscardPolicy implements RejectedExecutionHandler {
            /**
             * Creates a {@code DiscardPolicy}.
             */
            public DiscardPolicy() { }
    
            /**
             * Does nothing, which has the effect of discarding task r.
             *
             * @param r the runnable task requested to be executed
             * @param e the executor attempting to execute this task
             */
            public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            }
        }

     

  • DiscardOldestPolicy:拋棄隊列中等待最久的任務,然後把當前任務加入隊列中。
        public static class DiscardOldestPolicy implements RejectedExecutionHandler {
            /**
             * Creates a {@code DiscardOldestPolicy} for the given executor.
             */
            public DiscardOldestPolicy() { }
    
            /**
             * Obtains and ignores the next task that the executor
             * would otherwise execute, if one is immediately available,
             * and then retries execution of task r, unless the executor
             * is shut down, in which case task r is instead discarded.
             *
             * @param r the runnable task requested to be executed
             * @param e the executor attempting to execute this task
             */
            public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
                if (!e.isShutdown()) {
                    e.getQueue().poll();
                    e.execute(r);
                }
            }
        }

     

  • CallerRunsPolicy:調用任務的run()方法繞過線程池直接執行。
        public static class CallerRunsPolicy implements RejectedExecutionHandler {
            /**
             * Creates a {@code CallerRunsPolicy}.
             */
            public CallerRunsPolicy() { }
    
            /**
             * Executes task r in the caller's thread, unless the executor
             * has been shut down, in which case the task is discarded.
             *
             * @param r the runnable task requested to be executed
             * @param e the executor attempting to execute this task
             */
            public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
                if (!e.isShutdown()) {
                    r.run();
                }
            }
        }

     

使用方式如下代碼所示:

RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();

根據之前實現的線程工廠和拒絕策略,一個簡單線程池的相關示例代碼實現如下:

package Test;


import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class UserThreadPool {
    public static void main(String[] args) {
        //緩存隊列設置固定長度爲2,爲了快速觸發rejectHandler
        BlockingQueue queue = new LinkedBlockingQueue(2);

        //假設外部任務線程的來源有機房1和機房2混合調用
        UserThreadFactory f1 = new UserThreadFactory("第1機房");
        UserThreadFactory f2 = new UserThreadFactory("第2機房");

        UserRejectHandler handler = new UserRejectHandler();

        //核心線程爲1,最大線程爲2,爲了保證觸發rejectHandler
        ThreadPoolExecutor threadPoolFirst = new ThreadPoolExecutor(
                1, 2, 60, TimeUnit.SECONDS, queue, f1, handler);
        //利用第二個線程工廠實例創建第二個線程池
        ThreadPoolExecutor threadPoolSecond = new ThreadPoolExecutor(
                1, 2, 60, TimeUnit.SECONDS, queue, f2, handler);

        //創建400個任務線程
        Runnable task = new Task();
        for (int i = 0; i < 200; i++){
            threadPoolFirst.execute(task);
            threadPoolSecond.execute(task);
        }
    }
}

運行結果如下:

From UserThreadFactory's 第1機房 -Worker-1
From UserThreadFactory's 第2機房 -Worker-1
From UserThreadFactory's 第1機房 -Worker-2
From UserThreadFactory's 第2機房 -Worker-2
running_2
running_3
running_4
running_5
running_0
running_1
your task is reject. java.util.concurrent.ThreadPoolExecutor@1396fbe[Running, pool size = 2, active thread = 2,
 queued tasks =2,completed task =1]

當任務被拒絕時,拒絕策略會打印出當前線程池的大小已經達到了maximumPoolSize=2,且隊列已滿,完成的任務數提示已經有一個(最後一行)。

3、ThreadPoolExecutor部分源碼解析(execute()和addWorker())

在ThreadPoolExecutor的屬性定義中頻繁地用位移運算來表示線程池的狀態,位移運算是改變當前值的一種高效手段,包括左移和右移,下面從屬性定義開始閱讀源碼:

//Integer共有32位,最右邊29位表示工作線程數,最左邊3位表示線程池狀態
//注:簡單地說,3個二進制位可以表示從0到7的八個不同數值(第1處)    
private static final int COUNT_BITS = Integer.SIZE - 3;

//000-11111111111111111111111111111,類似於子網掩碼,用於位的與運算,
//得到左邊3位,還是右邊29位
private static final int CAPACITY = (1 << COUNT_BITS) - 1;

//用左邊3位,實現5種線程池狀態。
//111-00000000000000000000000000000,十進制:-536,870,912。
//此狀態表示線程池能夠接受新任務
private static final int RUNNING = -1 << COUNT_BITS;

//000-00000000000000000000000000000,十進制:0。
//此狀態表示不再接受新任務,但可以繼續執行隊列中的任務
private static final int SHUTDOWN  =  0 << COUNT_BITS;

//001-00000000000000000000000000000,十進制:536,870,912。
//此狀態表示全面拒絕,並中斷正在處理的任務
private static final int STOP  =  1 << COUNT_BITS;

//010-00000000000000000000000000000,十進制:1,073,741,824。
//此狀態表示所有任務已經被終止
private static final int TIDYING  =  2 << COUNT_BITS;

//011-00000000000000000000000000000,十進制:1,610,612,736。
//此狀態表示已清理完現場
private static final int TERMINATED  =  3 << COUNT_BITS;

//與運算,比如001-00000000000000000000000000011,表示67個工作線程,
//掩碼取反:111-00000000000000000000000000000,即得到左邊3位001,
//表示線程池當前處於STOP狀態
private static int runStateOf(int c)     { return c & ~CAPACITY; }

//同理掩碼000-111111111111111111111111111111,得到右邊29位,即工作線程數
private static int workerCountOf(int c)  { return c & CAPACITY; }

//把左邊3位與右邊29位按或運算,合併成一個值    
private static int ctlOf(int rs, int wc) { return rs | wc; }

第1處說明:線程池的狀態用高3位表示,其中包括了符號位。五種狀態的十進制值按從小到大依次排序爲:

RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED。

這樣設計的好處是可以通過比較值的大小來確定線程池的狀態,例如程序中經常出現isRunning的判斷:

private static boolean isRunning(int c){
    return c < SHUTDOWN;
}

Executor接口有且只有一個方法execute,通過參數傳入待執行線程的對象,下面分析ThreadPoolExecutor關於execute方法的實現:

    public void execute(Runnable command) {
        //JDK中,command的解釋爲:@param command the runnable task
        if (command == null)
            throw new NullPointerException();

        //返回包含線程數及線程池狀態的Integer類型數值
        int c = ctl.get();
        //如果工作線程數小於核心線程數,則創建線程任務並執行
        if (workerCountOf(c) < corePoolSize) {
            //addWorker是另一個極爲重要的方法,見下一段源碼解析(第1處)
            if (addWorker(command, true))
                return;
            //如果創建失敗,防止外部已經在線程池中加入新的任務,重新獲取一下
            c = ctl.get();
        }

        //只有當線程池處於RUNNING狀態,才執行後半句:置入隊列
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            //如果線程池不是RUNNING狀態,則將剛加入隊列的任務移除
            if (! isRunning(recheck) && remove(command))
                reject(command);
            //如果之前的線程已被消費完,新建一個線程
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //核心池和隊列都已滿,嘗試創建一個新線程
        else if (!addWorker(command, false))
            //如果addWorker返回是false,即創建失敗,則喚醒拒絕策略(第2處)
            reject(command);
    }
  • 第1處:execute方法在不同的階段有三次addWorker的嘗試動作。
  • 第2處:發生拒絕的理由有兩個:(1)線程池狀態爲非RUNNING狀態;(2)等待隊列已滿。

下面分析addWorker方法的源碼:

/**
     *根據當前線程池狀態,檢查是否可以添加新的任務線程,如果可以則創建並啓動任務
     * 如果一切正常且返回true。返回false的可能性如下:
     * 1、線程池沒有處於RUNNING狀態;
     * 2、線程工廠創建新的任務線程失敗
     *
     * firstTask:外部啓動線程池時所需要構造的第一個線程,它是線程的母體
     * core:新增工作線程時的判斷指標,解釋如下
     *      true  表示新增工作線程時,需要判斷當前RUNNING狀態的線程是否少於corePoolSize
     *      false 表示新增工作線程時,需要判斷當前RUNNING狀態的線程是否少於maximumPoolSize
     */
    private boolean addWorker(Runnable firstTask, boolean core) {
        //不需要任務預定義的語法標籤,響應下文的continue retry,快速推出多層嵌套循環(第1處)
        retry:
        for (;;) {
            //獲取線程池狀態及線程數
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            //如果是STOP及以上的狀態,或者firstTask初始線程不爲空,或者隊列爲空都會直接返回創建失敗(第2處)
            if (rs >= SHUTDOWN &&
                    ! (rs == SHUTDOWN &&
                            firstTask == null &&
                            ! workQueue.isEmpty()))
                return false;
            
            for (;;) {
                //如果超過最大允許線程數則不能再添加新的線程
                //最大線程數不能超過2^29,否則影響左邊3位的線程池狀態值
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                        wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                //將當前活動線程數+1(第3處)
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                //線程池狀態和工作線程數是可變化的,需要經常提取這個最新值
                c = ctl.get();  // Re-read ctl
                //如果已經關閉,則再次從retry標籤處進入,在第2處再做判斷(第4處)
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
                //如果線程還是處於RUNNING狀態,那就在說明僅僅是第3處失敗
                //繼續循環執行(第5處)
            }
        }

        //開始創建工作線程
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            //利用Worker構造方法中的線程池工廠創建線程,並封裝成工作線程Worker對象
            w = new Worker(firstTask);
            //注意這是Worker中的屬性對象thread(第6處)
            final Thread t = w.thread;
            if (t != null) {
                //在進行ThreadPoolExecutor的敏感操作時都需要持有主鎖
                //避免在添加和啓動線程時被幹擾
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    //當線程池狀態爲RUNNING或SHUTDOWN
                    //且firstTask初始線程爲空時
                    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) {
                    //終於看到親切的start()方法
                    //注意,並非線程池的execute的command參數指向的線程
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                //線程啓動失敗,把剛纔第3處加上的工作線程計數再減回去
                addWorkerFailed(w);
        }
        return workerStarted;
    }
  • 第1處:配合循環語句出現的label,類似於goto作用。label定義時,必須把標籤和冒號的組合語句緊緊相鄰定義在循環體之前,否則會編譯出錯。目的是實現多重循環時能夠快速推出到任何一層。這種做法的出發點似乎非常貼心,但是在大型軟件項目中,濫用標籤行跳轉的後果將是災難性的。示例代碼中,在retry下方有兩個無限循環,在workerCount加1成功後,直接推出兩層循環。
  • 第3處:與第1處的標籤呼應,AtomicInteger對象的加1操作時原子性的。break retry表示直接跳出與retry相鄰的這個循環體。
  • 第4處:此continue跳轉至標籤處,繼續執行循環。如果條件爲假,則說明線程池還處於運行狀態,即繼續在for(;;)循環內執行。
  • 第5處:compareAndIncrementWorkerCount方法執行失敗的概率非常低。即使失敗,再次執行時成功的概率也是極高的,類似於自旋鎖原理。這裏的處理邏輯是先加1,創建失敗減1,這是輕量級處理併發創建線程的方式。如果先創建線程,成功再加1,當發現超出限制後再銷燬線程,那麼這樣的處理方式明顯比前者代價要大。
  • 第6處:Worker對象時工作線程的核心類實現,部分源碼如下:
    //它實現Runnable接口,並把本對象作爲參數輸入給run()方法中的runWorker(this),
    //所以內部屬性線程thread在start的時候,即會調用runWorker方法
    private final class Worker extends AbstarctQueueSynchronizer implements Runnable{
        Worker(Runnable firstTask){
            //它是AbstractQueueSynchronizer的方法
            //在runWorker方法執行之前禁止線程被中斷
            setState(-1);
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }
    
        //當thread被start()之後,執行runWorkder的方法
        public void run(){
            runWorker(this);
        }
    }

     

碼出高效中作者的建議:使用線程池要注意以下幾點:

  1. 合理設置各類參數,應根據實際業務場景來說設置合理地工作線程數。
  2. 線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。
  3. 創建線程或線程池時請指定有意義的線程名稱,方便出錯時回溯。

阿里的Java開發手冊中明確提出:線程池不允許使用Executors,而是通過ThreadPoolExecutor的方式創建,這樣的處理方式能更加明確線程池的運行規則,規避資源耗盡的風險(Executors不能指定所有的參數,默認參數可能會造成資源浪費)。

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