ThreadPoolExecutor解析-主要源碼研究

注:本文的分析和源碼基於jdk1.7;

一、ThreadPoolExecutor創建

ThreadPoolExecutor作爲java.util.concurrent包中核心的類,先看下類型的結構:

UML類圖

 

最頂級的接口都是Executor,而ThreadPoolExecutor繼承於抽象類AbstractExecutorService,提供以下4個構造函數用於創建:

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,ong keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue);

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory)

 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler)

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

 

前面的3個方法都是使用通過this調用最後一個方法,沒有指定的構造參數使用默認參數,參數解析:

1、

    /**
     * Core pool size is the minimum number of workers to keep alive
     * (and not allow to time out etc) unless allowCoreThreadTimeOut
     * is set, in which case the minimum is zero.
     */
    private volatile int corePoolSize;

線程池核心線程數大小,初始化時核心線程數也是0,除非先調用prestartCoreThread或者prestartAllCoreThreads先創建核心線程;在沒有設置allowCoreThreadTimeOut爲true情況下,核心線程不會銷燬;

2、

    /**
     * Maximum pool size. Note that the actual maximum is internally
     * bounded by CAPACITY.
     */
    private volatile int maximumPoolSize;

線程池線程數最大值,達到最大值後線程池不會再增加線程執行任務,任務會進入等待隊列或者由拒絕策略處理;

該值實際的可設置最大值不是Integer.MAX_VALUE,而是常量CAPACITY(後面再解析常量)

3、

    /**
     * Timeout in nanoseconds for idle threads waiting for work.
     * Threads use this timeout when there are more than corePoolSize
     * present or if allowCoreThreadTimeOut. Otherwise they wait
     * forever for new work.
     */
    private volatile long keepAliveTime;

空閒工作線程的空閒時間;超過corePoolSize的線程或者allowCoreThreadTimeOut爲true的主線程使用這個作爲超時時間;

否則線程一直等待任務或關閉;

4、

    /**
     * The queue used for holding tasks and handing off to worker
     * threads.  We do not require that workQueue.poll() returning
     * null necessarily means that workQueue.isEmpty(), so rely
     * solely on isEmpty to see if the queue is empty (which we must
     * do for example when deciding whether to transition from
     * SHUTDOWN to TIDYING).  This accommodates special-purpose
     * queues such as DelayQueues for which poll() is allowed to
     * return null even if it may later return non-null when delays
     * expire.
     */
    private final BlockingQueue<Runnable> workQueue;

  這個隊列用於保存任務以及爲工作線程提供待執行的任務;不要求該隊列的poll方法返回null表示該隊列爲空,隊列是否爲空(用於決定是否從shutdown狀態變爲tidying狀態)僅僅依靠isEmpty方法判斷;這是爲了兼容延時隊列poll方法可能返回爲null,但在延時到期實際返回時非空;

5、

    /**
     * Factory for new threads. All threads are created using this
     * factory (via method addWorker).  All callers must be prepared
     * for addWorker to fail, which may reflect a system or user's
     * policy limiting the number of threads.  Even though it is not
     * treated as an error, failure to create threads may result in
     * new tasks being rejected or existing ones remaining stuck in
     * the queue.
     *
     * We go further and preserve pool invariants even in the face of
     * errors such as OutOfMemoryError, that might be thrown while
     * trying to create threads.  Such errors are rather common due to
     * the need to allocate a native stack in Thread#start, and users
     * will want to perform clean pool shutdown to clean up.  There
     * will likely be enough memory available for the cleanup code to
     * complete without encountering yet another OutOfMemoryError.
     */
    private volatile ThreadFactory threadFactory;

線程工廠,線程生成器;所有線程通過這個工廠創建(通過 addWorker方法)任何調用都要做好可能由系統或者用戶設置的線程數限制策略導致的創建失敗;雖然失敗不能被當做錯誤處理,但是創建線程失敗可能導致新任務被拒絕,已經存在任務繼續阻塞在隊列裏等待執行;

 

6、

    /**
     * Handler called when saturated or shutdown in execute.
     */
    private volatile RejectedExecutionHandler handler;

任務拒絕策略;負責處理當線程飽和後(線程數達到最大,等待隊列滿了)、線程池正在關閉時的新提交任務;

ThreadPoolExecutor內部有實現4個拒絕策略:

(1)、CallerRunsPolicy,由調用execute方法提交任務的線程來執行這個任務;

(2)、AbortPolicy,拋出異常RejectedExecutionException拒絕提交任務;

(3)、DiscardPolicy,直接拋棄任務,不做任何處理;

(4)、DiscardOldestPolicy,去除任務隊列中的第一個任務(最舊的),重新提交;

推薦使用前面兩種拒絕策略,特別是對於不知道如何使用或第一次使用線程池;當然也可以自己實現拒絕策略,

只要繼承java.util.concurrent.RejectedExecutionHandler接口;

該值默認是AbortPolicy;

 

通過上面的4個構造函數創建完線程池後,就可以通過submit或execute方法提交任務;

工作完成後當然最後要關閉線程池,可以調用下面兩個方法:

shutdown():對已經在執行的線程進行關閉,不再接收新的任務;

shutdownNow():嘗試停止正在執行的線程,會清除任務隊列中的任務;

以上兩個方法都不會等到所有任務完成後關閉,需要通過awaitTermination(long, TimeUnit)方法實現;

二、以下通過部分源碼研究分析,是按照我自己的思路往下寫,不一定符合每個人的理解順序;

ThreadPoolExecutor線程池的狀態:

    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;

其中COUNT_BITS是 int 位數

private static final int COUNT_BITS = Integer.SIZE - 3;  //Integer.SIZE=32

所以實際 COUNT_BITS = 29,

用上面的5個常量表示線程池的狀態,實際上是使用32位中的高3位表示;後面還會講到這些常量;

高3位:

RUNNING=111

SHUTDOWN=000

STOP=001

TIDYING=010

TERMINATED=110

//線程池最大線程數=536870911(2^29-1)

private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

前面的構造函數雖然傳入的maximumPoolSize是個int值,但是實際最大值是這個;

這樣線程池的狀態和線程數量就由一個變量存儲:

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); //使用AtomicInteger 當然是爲了保證多線程同步問題

ctl 可以理解爲control(控制),初始值爲線程數0,狀態RUNNING:

private static int ctlOf(int rs, int wc) { return rs | wc; }

接下來通過狀態來看線程池的運行:

完成線程的創建後,首先通過 submit 或者 execute 方法提交,實際submit 方法也是內部調用 execute方法;我們只看這個方法:

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        
        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);
    }

這個方法主要有三個邏輯(if...if...else):

1、判斷當前的線程數是否小於corePoolSize如果是,使用入參任務通過addWord方法創建一個新的線程,如果能完成新線程創建exexute方法結束,成功提交任務;

2、在第一步沒有完成任務提交;狀態爲運行並且能成功加入任務到工作隊列後,再進行一次check,如果狀態在任務加入隊列後變爲了非運行(有可能是在執行到這裏線程池shutdown了),非運行狀態下當然是需要reject;然後再判斷當前線程數是否爲0(有可能這個時候線程數變爲了0),如是,新增一個線程;

3、如果不能加入任務到工作隊列,將嘗試使用任務新增一個線程,如果失敗,則是線程池已經shutdown或者線程池已經達到飽和狀態,所以reject;

從上面新增任務的execute方法也可以看出,拒絕策略不僅僅是在飽和狀態下使用,在線程池進入到關閉階段同樣需要使用到;

上面的幾行代碼還不能完全清楚這個新增任務的過程,肯定還需要清楚addWorker方法纔行:

private boolean addWorker(Runnable firstTask, boolean core) {//firstTask:新增一個線程並執行這個任務,可空,增加的線程從隊列獲取任務;core:是否使用corePoolSize作爲上限,否則使用maxmunPoolSize</span>
        retry: //較少使用到的標識符,用於重試
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))//線程狀態非運行並且當非shutdown狀態下任務爲空且隊列非空;
                return false;  //判斷條件有點難理解,其實是非運行狀態下(>=SHUTDOWN)或者SHUTDOWN狀態下任務非空(新提交任務)、任務隊列爲空,就不可以再新增線程了(return false),即SHUTDOWN狀態是可以新增線程去執行隊列中的任務;
            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize)) //實際最大線程數是CAPACITY;
                    return false;
                if (compareAndIncrementWorkerCount(c)) //AtomicInteger的CAS操作;
                    break retry;                       //新增線程數成功,結束retry(retry下的for循環)
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs) //狀態發生改變,重試retry;
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            final ReentrantLock mainLock = this.mainLock;
            w = new Worker(firstTask); // Worker爲內部類,封裝了線程和任務,通過ThreadFactory創建線程,可能失敗拋異常或者返回null
            final Thread t = w.thread;
            if (t != null) {
                mainLock.lock();
                try {
                    int c = ctl.get();
                    int rs = runStateOf(c);

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // SHUTDOWN以後的狀態和SHUTDOWN狀態下firstTask爲null,不可新增線程
                            throw new IllegalThreadStateException();
                        workers.add(w); //保存到一個HashSet中
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s; //記錄最大線程數
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);//失敗回退,從wokers移除w,線程數減一,嘗試結束線程池(調用tryTerminate方法,後續解析)
        }
        return workerStarted;
    }

通過以上兩個方法,對於新增任務應該是比較清楚了,新增任務可以說是對線程池最常的操作了;

接下來解析下可能使用到的關閉方法:shutdown,shutdownNow

public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess(); //這個方法校驗線程訪問許可,不是很理解,後面有時間再單獨解析;
            advanceRunState(SHUTDOWN); //轉換線程池狀態爲SHUTDOWN
            interruptIdleWorkers(); //中斷所有空閒的線程
            onShutdown(); // 空實現方法,是做shutdown清理操作的
        } finally {
            mainLock.unlock();
        }
        tryTerminate(); //嘗試結束線程池(設置狀態爲TERMINATED)
 }

 

public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();//同上
            advanceRunState(STOP);//轉換線程池狀態到STOP
            interruptWorkers();//中斷所有線程
            tasks = drainQueue();//獲取到任務隊列所有任務,並清空隊列
        } finally {
            mainLock.unlock();
        }
        tryTerminate();//同上
        return tasks;
    }

由上可知,兩個關閉方法的區別:

1、shutdown設置狀態爲SHUTDOWN,而shutdownNow設置狀態爲STOP;

2、shutdown值中斷空閒的線程,已提交的任務可以繼續被執行,而shutdownNow中斷所有線程;

3、shutdown無返回值,shutdownNow返回任務隊列中還未執行的任務;

爲了更新深入理解,我們再來看下 tryTerminate方法

final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))  //保證只有SHUTDOWN狀態下任務隊列爲空和STOP狀態下才以嘗試終止
                return;
            if (workerCountOf(c) != 0) { //線程數還不是0情況下不可結束線程池
                interruptIdleWorkers(ONLY_ONE); //只爲了中斷一個線程?還不是非常理解設計者的意思
                return;
            }

            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) { //CAS操作設置TIDYING狀態,注意這裏處於循環中,失敗會重設的
                    try {
                        terminated(); //空實現方法
                    } finally {
                        ctl.set(ctlOf(TERMINATED, 0));//最終狀態TERNINATED
                        termination.signalAll();//可重入鎖的condition,通知所有wait,後面會有看到
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
        }
    }


雖然有shutdown和shutdownNow方法,但是還是不能滿足一個需求:就是需要知道等待所有任務已完成線程池結束

這裏ThreadPoolExecutor提供了awaitTermination方法滿足這個需求:

public boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (;;) {
                if (runStateAtLeast(ctl.get(), TERMINATED))
                    return true;
                if (nanos <= 0)
                    return false;
                nanos = termination.awaitNanos(nanos);
            }
        } finally {
            mainLock.unlock();
        }
    }

這個方法兩個入參,設置等待超時時間;

如果狀態已經是TERMINATED返回true,表示已關閉;

否則一直等到termination的signalAll至超時或者當前線程中斷;超時後都線程池都沒有關閉,返回false;

 

在上面提到有幾個空實現的方法,這些方法不是沒有用處的,當有需要繼承ThreadPoolExecutor類時,

按需要實現這個方法是最好的了;後面有時間繼續分享一些關於繼承這個類實現不同需求的分析;

 

希望通過對部分源碼的解析,能幫助到自己和其他人更好理解以及試用ThreadPoolExecutor;

對源碼只分析上面主要的一些方法,這裏不再一一列出,有興趣的可以閱讀完整的代碼;

如果有其他問題或者有寫的不對的地方,還請評論;
 

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