ThreadPoolExecutor原始碼解析(一)

zzzzMing發表於2018-05-16

 1.ThreadPoolExcuter原理說明

  首先我們要知道為什麼要使用ThreadPoolExcuter,具體可以看看文件中的說明:
  執行緒池 ThreadPoolExcuter 可以解決兩個不同問題:由於減少了每個任務的呼叫開銷,在執行大量的非同步任務時,它通常能夠提供更好的效能,並且還可以提供繫結和管理資源(包括執行集合任務時使用的執行緒)的方法。每個 ThreadPoolExecutor還維護著一些基本的統計資料,如完成的任務數。
  執行緒池 ThreadPoolExcuter 做的其實可以看得很簡單,其實就是把你提交的任務(task)進行排程管理執行,但這個排程的過程以及其中的狀態控制是比較複雜的。

2.初始化引數介紹
可以直接看最完整的ThreadPoolExcuter的初始化函式:

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

逐個介紹如下:
corePoolSize:核心執行緒數,在ThreadPoolExcutor中有一個與它相關的配置:allowCoreThreadTimeOut(預設為false),當allowCoreThreadTimeOut為false時,核心執行緒會一直存活,哪怕是一直空閒著。而當allowCoreThreadTimeOut為true時核心執行緒空閒時間超過keepAliveTime時會被回收。

maximumPoolSize:最大執行緒數,執行緒池能容納的最大執行緒數,當執行緒池中的執行緒達到最大時,此時新增任務將會採用拒絕策略,預設的拒絕策略是丟擲一個執行時錯誤(RejectedExecutionException)。值得一提的是,當初始化時用的工作佇列為LinkedBlockingDeque時,這個值將無效。

keepAliveTime:存活時間,當非核心空閒超過這個時間將被回收,同時空閒核心執行緒是否回收受allowCoreThreadTimeOut影響。

unit:keepAliveTime的單位。

workQueue:任務佇列,常用有三種佇列,即SynchronousQueue,LinkedBlockingDeque(無界佇列),ArrayBlockingQueue(有界佇列)。

threadFactory:執行緒工廠,ThreadFactory是一個介面,用來建立worker。通過執行緒工廠可以對執行緒的一些屬性進行定製。預設直接新建執行緒。

RejectedExecutionHandler:也是一個介面,只有一個方法,當執行緒池中的資源已經全部使用,新增新執行緒被拒絕時,會呼叫RejectedExecutionHandler的rejectedExecution法。
預設是丟擲一個執行時異常。

  這麼多引數看起來好像很複雜,所以Java貼心得為我們準備了便捷的API,即可以直接用Executors建立各種執行緒池。分別是:

        //建立一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閒執行緒,若無可回收,則新建執行緒。
     //通過設定corePoolSize為0,而maximumPoolSize為Integer.Max_VALUE(Int型資料最大值)實現。
     ExecutorService cache = Executors.newCachedThreadPool();
     //建立一個定長執行緒池,可控制執行緒最大併發數,超出的執行緒會在佇列中等待。
     //通過將corePoolSize和maximumPoolSize的值設定為一樣的值來實現。 ExecutorService fixed
= Executors.newFixedThreadPool(num);
     //建立一個定長執行緒池,支援定時及週期性任務執行。
     //通過將佇列引數workQueue設定為DelayWorkQueue來實現。 ExecutorService schedule
= Executors.newScheduledThreadPool(5);
     //建立一個單執行緒化的執行緒池,它只會用唯一的工作執行緒來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先順序)執行。
     //通過將corePoolSize和maximumPoolSize都設定為1來實現。 ExecutorService single
= Executors.newSingleThreadExecutor();

   這幾個API會根據具體的情況而使用預設定好預設的初始化引數去建立一個ThreadPoolExecutor。

  這裡需要做一個額外說明,在ThreadPoolExcuter中,worker和task是有區別的,task是使用者提交的任務,而worker則是用來執行task的執行緒。在初始化引數中,corePoolSize和maximumPoolSize都是針對worker的,而workQueue是用來存放task的。

3.worker介紹

  前面有介紹了一下worker和task的區別,其中task是使用者提交的執行緒任務,而worker則是ThreadPoolExecutor自己內部實現的一個類了。

  具體原始碼如下:

  

    /**
     * Woker主要維護著執行task的worker的中斷控制資訊,以及其他小記錄。這個類擴充AbstractQueuedSynchronizer
     * 而來簡化獲取和釋放每一個任務執行中的鎖。這可以防止中斷那些打算喚醒正在等待其他執行緒任務的任務,而不是
     * 中斷正在執行的任務。我們實現一個簡單的不可重入鎖而不是ReentrantLo,因為我們不想當其呼叫setCorePoolSize
     * 這樣的方法的時候能獲得鎖。
     */
    //worker主要是對進行中的任務進行中斷控制,順帶著對其他行為進行記錄
    private final class Worker
            extends AbstractQueuedSynchronizer
            implements Runnable
    {
        /**
         * This class will never be serialized, but we provide a
         * serialVersionUID to suppress a javac warning.
         */
        private static final long serialVersionUID = 6138294804551838833L;

        /** Thread this worker is running in.  Null if factory fails. */
        //正在跑的執行緒,如果是null標識factory失敗
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        //初始化一個任務以執行
        Runnable firstTask;
        /** Per-thread task counter */
        //每個執行緒計數
        volatile long completedTasks;

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


        /** Delegates main run loop to outer runWorker  */
        //主要呼叫了runWorker
        public void run() {
            runWorker(this);
        }

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

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

        //嘗試獲取鎖
        protected boolean tryAcquire(int unused) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        //嘗試釋放鎖
        protected boolean tryRelease(int unused) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

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

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

   ThreadPoolExcuter 中的Worker其實可以看作高階一點的執行緒。其中繼承AbstractQueuedSynchronizer主要是為了實現鎖控制。ThreadPoolExecutor會持有並管理Worker,在Worker中firstTask其實就是存放task的,而thread則是存放當前Worker本身的執行緒。

其中比較重要的就是run方法了,但這個方法其實又是去呼叫ThreadPoolExecutor裡面的runWorker()方法,具體可以看下一節的介紹。

4.ctl介紹以及執行狀態說明
  首先需要介紹執行緒池有五種執行狀態:
RUNNING(狀態值-1): 接收新任務並處理佇列中的任務
SHUTDOWN(狀態值0): 不接收新任務但會處理佇列中的任務。
STOP(狀態值1): 不接收新任務,不處理佇列中的任務,並中斷正在處理的任務
TIDYING(狀態值2): 所有任務已終止,workerCount為0,處於TIDYING狀態的執行緒將呼叫鉤子方法terminated()。
TERMINATED(狀態值3): terminated()方法完成。

  然後我們可以看看ThreadPoolExcuter中的ctl這個變數。
  ctl是ThreadPoolExcuter中比較有意思的一個實現,它是一個AtomicInteger,這裡不對AtomicInteger多做討論,只要知道可以把它看成有原子性的Integer就夠了,其實它具有原子性的原理是使用了CAS的技術,這是一種樂觀鎖的具體實現。
  ThreadPoolExcuter是將兩個內部值打包成一個值,即將workerCount和runState(執行狀態)這兩個值打包在一個ctl中,因為runState有5個值,需要3位,所以有3位表示
runState,而其他29位表示為workerCount。
  而執行時要獲取其他資料時,只需要對ctl進行拆包即可。具體這部分程式碼如下:

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

    // Packing and unpacking ctl  
  //拆包ctl,分別獲取runState和WorkerCount
private static int runStateOf(int c) { return c & ~CAPACITY; } private static int workerCountOf(int c) { return c & CAPACITY; }
  //打包操作
private static int ctlOf(int rs, int wc) { return rs | wc; }

5.拒絕策略

當執行器(Executor)處於終止狀態,或者執行器在max threads和工作佇列都是有界並且處於飽和的時候,新提交的任務會被拒絕。在任一情況下,執行的任務將呼叫RejectedExecutionHandler的方法rejectedExecution(Runnable, ThreadPoolExecutor)。有以下四種拒絕策略:

1.預設的是ThreadPoolExecutor.AbortPolicy,在這種策略下,處理器會在拒絕後丟擲一個執行異常RejectedExecutionException。

2.在ThreadPoolExecutor.CallerRunsPolicy的策略下,執行緒會呼叫它直接的execute來執行這個任務。這種方式提供簡單的反饋控制機制來減緩新任務提交的速度。

3.在ThreadPoolExecutor.DiscardPolicy策略下,無法執行的任務將被簡單得刪除掉。

4.在ThreadPoolExecutor.DiscardOldestPolicy策略下,如果executor沒有處於終止狀態,在工作佇列頭的任務將被刪除,然後會重新執行(可能會再次失敗,這會導致重複這個過程)。

 

總結:本篇初步介紹了ThreadPoolExcuter的基本原理,解決了什麼問題。而後說明了ThreadPoolExcuter中的初始化引數,對其中的各個引數做初步介紹。再之後介紹ctl變數的作用,並初步介紹了任務提交失敗後的拒絕策略。

 

相關文章