15-Executor框架

1. 簡介

1.1 Executor框架的兩級調度模型

       在HotSpot JVM的線程模型中,Java線程被一對一映射爲本地操作系統線程。

       Java線程啓動時會創建一個本地操作系統線程;當該Java線程終止時,這個操作系統線程也會被回收。

       在上層,Java多線程程序通常把應用分解爲若干任務,然後由用戶級的調度器(Executor)將這些任務映射爲固定數量的線程;

       在底層,操作系統內核將這些線程映射到硬件處理器上。

在這裏插入圖片描述

1.2 Executor框架的結構與成員

1.2.1 Executor 框架的結構

       Executor框架主要由3大部分組成:

  1. 任務。包括被執行任務需要實現的接口:Runnable接口或Callable接口
  2. 任務的執行。包括任務執行機制的核心接口Executor,以及繼承自ExecutorExecutorService接口。
  3. 異步計算的結果。包括接口Future 和實現Future接口的FutureTask類。

在這裏插入圖片描述

  • Executor是一個接口,是Executor框架的基礎,它將任務的提交與任務的執行分離開來
  • ThreadPoolExecutor 是線程池的核心實現類,用來執行被提交的任務。
  • ScheduleThreadPoolExecutor 是一個實現類,可以在給定的延遲後運行命令,或者定期執行命令。
  • Future 接口和實現 Future接口的FutureTask類,代表異步計算的結果。
  • Runnable 接口和Callable接口的實現類,都可以被 ThreadPoolExecutorScheduleThreadPoolExecutor執行。

在這裏插入圖片描述

1.2.2 Executor 框架的成員

(1)ThreadPoolExecutor

       ThreadPoolExecutor通常使用工廠類Executors來創建。

       Executors可以創建3種類型的ThreadPoolExecutor

       SingleThreadExecutorFixedThreadPoolCachedThreadPool

       1. SingleThreadExecutor:創建使用單個線程SingleThreadExecutorAPI

public static ExecutorService newSingleThreadExecutor()
    
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)    

       適用於需要保證順序地執行各個任務;並且在任意時間點,不會有多個線程是活動的應用場景。

       2.FixedThreadPool :創建使用固定線程數FixedThreadPoolAPI

public static ExecutorService newFixedThreadPool(int nThreads)
    
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)    

       適用於爲了滿足資源管理的需求,需要限制當前線程數量的應用場景,適用於負載比較重的服務器。

       3.CachedThreadPool:創建一個會根據需要創建新線程CachedThreadPoolAPI

public static ExecutorService newCachedThreadPool()
    
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)    

       是大小無界的線程池,適用於執行很多的短期異步任務的程序或負載較輕的服務器。

(2)ScheduledThreadPoolExecutor

       1.ScheduleThreadPoolExecutor:包含若干個線程的ScheduledThreadPoolExecutor

       2.SingleThreadScheduleExecutor:只包含一個線程的ScheduledThreadPoolExecutor

       ScheduledThreadPoolExecutor適用於需要多個後臺線程執行週期任務,同時爲了滿足資源管理需求而需要限制後臺線程的數量的應用場景。

       SingleThreadScheduleExecutor 適用於需要單個後臺線程執行週期任務,同時需要保證順序地執行各個任務地應用場景。

(3)Future 接口

       Future接口和實現Future接口的FutureTask類用來表示異步計算地結果。

(4)Runnable接口和Callable接口

       可以使用工廠類Executors把一個Runnable包裝成一個Callable

public static Callable<Object> callable(Runnable task)

public static <T> Callable<T> callable(Runnable task, T result)

2. ThreadPoolExecutor 詳解

       它是線程池的實現類。

public class ThreadPoolExecutor extends AbstractExecutorService {
    
    private volatile int corePoolSize;
    private volatile int maximumPoolSize;
    private final BlockingQueue<Runnable> workQueue;
    private volatile RejectedExecutionHandler handler;
    
    // ......
}

2.1 SingleThreadExecutor 詳解

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

在這裏插入圖片描述
       可以看到,SingleThreadExecutor 正是由於參數corePoolSizemaximumPoolSize的值爲1,所以該線程池纔始終只有一個線程。

       需要注意的是LinkedBlockingQueue並不是真的沒有長度限制,其最大長度爲2^31-1

2.2 FixedThreadPool 詳解

return new ThreadPoolExecutor(nThreads, nThreads,
                              0L, TimeUnit.MILLISECONDS,
                              new LinkedBlockingQueue<Runnable>());

       keepAliveTime設置爲0L,意味着多餘的空閒線程會被立即終止。

       FixedThreadPool 使用無界隊列作爲工作隊列,有如下影響:

       1)當線程池中的線程數達到 corePoolSize 後,新任務將在無界隊列中等待,因此線程池中的線程數不會超過corePoolSize

       2)使用無界隊列,maxnumPoolSize 無效。

       3)由於 1)和 2),keepAliveSize 無效。

       4)不會拒絕任務。

2.3 CachedThreadPool 詳解

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

在這裏插入圖片描述
       SynchronousQueue是一個沒有容量的阻塞隊列。每個插入操作必須等待另一個線程的對應移除操作,反之亦然。

在這裏插入圖片描述

3. ScheduleThreadPoolExecutor 詳解

       主要用來在給定的延遲之後運行任務,或者定期執行任務。

3.1 運行機制

在這裏插入圖片描述
       ScheduleThreadPoolExecutor 爲了實現週期性的執行任務,對ThreadPoolExecutor 做了如下的修改:

  • 使用DelayQueue作爲任務隊列
  • 獲取任務的方式不同
  • 執行週期任務後,增加了的處理

3.2 實現原理

       ScheduledThreadPoolExecutor 把待調度的任務(ScheduledFutureTask)放到一個DelayQueue中.

private class ScheduledFutureTask<V>
            extends FutureTask<V> implements RunnableScheduledFuture<V> {

       
        // 表示這個任務被添加到 ScheduledThreadPoolExecutor 中的序號
        private final long sequenceNumber;

        // 表示這個任務將要被執行的具體時間
        private long time;

		// 表示任務執行的間隔週期
        private final long period;
        
        // ......
}

       DelayQueue封裝了一個 PriorityQueue,這個PriorityQueue 會對隊列中的ScheduledFutureTask進行排序。排序是,time 小的排在前面(時間早的任務將被先執行)。如果兩個ScheduledFutureTasktime相同,就比較 sequenceNumbersequenceNumber 小的排在前面(任務執行時間相同,先提交的任務先執行)。

在這裏插入圖片描述
       1)線程1從 DelayQueue中獲取已到期的 ScheduledFutureTask(DelayQueue.take() )。到期任務指 ScheduledFutureTasktime大於等於當前時間。

       2)線程1 執行這個ScheduleFutureTask

       3)線程1修改ScheduledFutureTasktime 變量爲下次將要執行的時間。

       4)線程1 把這個修改time之後的ScheduledFutureTask 放回 DelayQueue中。

       獲取任務的過程

public E take() throws InterruptedException {
    // 獲取鎖
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        for (;;) {
            E first = q.peek();
            if (first == null)
                //說明隊列爲空調用condition.await()方法,會使得當前線程釋放lock然後加入到等待隊列中
                available.await();
            else {
                // 如果第一個數據不爲空 
                // 獲取消息體的延遲時間(getDelay() 會在消息體內重寫 自定義添加延遲時間)
                long delay = first.getDelay(NANOSECONDS);
                
                if (delay <= 0)
                    //如果延遲時間 小於等於0 說明已經達到了延遲時間 調用poll方法返回消息體
                    return q.poll();
                
                //如果延遲時間不爲空的話 說明還需要等待一段時間 此時重新循環 所以將frist置爲空
                first = null; // don't retain ref while waiting
                
                if (leader != null)
                    available.await();
                else {
                    Thread thisThread = Thread.currentThread();
                    leader = thisThread;
                    try {
                        available.awaitNanos(delay);
                    } finally {
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }
    } finally {
        if (leader == null && q.peek() != null)
            // 喚醒睡眠的線程
            available.signal();
        lock.unlock();
    }
}

       1)獲取Lock

       2)獲取週期任務

              1、如果 PriorityQueue 爲空,當前線程到 Condition 中等待;

              2、如果 PriorityQueue 的頭元素的time 時間比當前時間早,到Condition中等待到time時間。否則返回任務。

              3、獲取 PriorityQueue 的頭元素,如果PriorityQueue 不爲空,則喚醒在 Condition 中等待的所有線程

       3)釋放鎖

       添加任務到隊列

public boolean offer(E e) {
    final ReentrantLock lock = this.lock;
    // 獲取鎖
    lock.lock();
    try {
        // 向 PriorityQueue 添加任務
        q.offer(e);
        if (q.peek() == e) {
            leader = null;
            // 如果添加的元素是頭元素,則喚醒在 Condition 中等待的所有線程
            available.signal();
        }
        return true;
    } finally {
        // 釋放鎖
        lock.unlock();
    }
}

4. FutureTask 詳解

       Future 接口和實現 Future接口的FutureTask類,代表異步計算的結果。

4.1 簡介

在這裏插入圖片描述

       除了實現Future接口外,還實現了Runnable接口。因此FutureTask 可以交給Executore執行,也可以調用線程直接執行(FutureTask.run())。根據 FutureTask.run()方法被執行的時機,FutureTask 可以由如下3種狀態:

在這裏插入圖片描述
       當FutureTask處於未啓動狀態時,執行FutureTask.cancel()方法,將導致調用線程阻塞。當FutureTask處於已完成狀態時,執行FutureTask.get()方法將導致調用線程立即返回結果或拋出異常。

       當FutureTask處於未啓動狀態時,執行 FutureTask.cancel()方法將導致此任務用於不會被執行。當FutureTask處於已啓動狀態時,執行 FutureTask.cancel(true)方法將以中斷執行此任務線程的方式試圖停止任務;當FutureTask 處於已啓動狀態時,執行FutureTask.cancel(false)方法將不會對正在運行的線程產生影響(讓正在執行的任務運行完成);當FutureTask處於已完成狀態時,執行FutureTask.cancel(..)方法將返回false

在這裏插入圖片描述

4.2 實現原理

       FutureTask 的實現基於 AbstractQueuedSynchronizer(AQS)AQS 是一個同步框架,它提供通用機制來原子性管理同步狀態、阻塞和喚醒線程,以及被阻塞線程的隊列。

       每一個基於 AQS實現的同步器都會包含兩種類型的操作:

       1、至少一個acquire操作。這個操作阻塞調用線程,除非直到 AQS的狀態運行這個線程繼續執行。FutureTaskacquire操作爲get()/get(long timeout,TimeUnit unit)方法調用。

       2、至少一個 release操作。這個操作改變AQS的狀態,改變後的狀態可運行一個或多個阻塞線程被解除阻塞。FutureTaskrelease操作包括run()方法和cancel()方法。

在這裏插入圖片描述

       SyncFutureTask 的內部私有類,它繼承自AQS。創建FutureTask時會創建內部私有的成員對象SyncFutureTask 所有的公有方法直接委託給了內部私有的Sync

       FutureTask.get() 方法會調用 AQS.acquireSharedInterruptibly(int arg)方法,執行過程如下:

              1)調用 AQS.acquireSharedInterruptibly(int arg)方法,這個方法首先會回調在子類Sync中實現的tryAcquireShared()方法來判斷 acquire 操作是否可以成功。成功的條件是:state 爲執行完成狀態RAN或已取消狀態CANCELLED,且runner 不爲null

              2)如果成功則 get () 方法立即返回。如果失敗,則到線程等待隊列中去等待其他線程執行release 操作。

              3)當其他線程執行 release操作(如 FutureTask.run()FutureTask.cancel(...) 喚醒當前線程後,當前線程再次執行tryAcquireShared()將返回值1,當前線程將離開線程等待隊列並喚醒它的後繼線程(產生級聯喚醒)。

              4)最後返回的計算結果或者拋出異常。

       FutureTask.run()的執行過程如下:

              1)執行在構造函數中指定的任務(Callable.call()

              2)以原子的方式更新同步狀態(調用AQS.compareAndState(int expect,int update)),設置state 爲執行完成RAN)。如果這個原子操作成功,就設置代表計算結果的變量result的值爲Callable.call()的返回值,然後調用AQS.releaseShared(int arg)

              3)AQS.releaseShared(int arg)首先會回調在子類Sync中實現的tryReleaseShared(arg)來執行release操作(設置運行任務的線程 runnernull,然後返回true);AQS.releaseShared(int arg),然後喚醒線程等待隊列中的第一個線程。

              4)調用 FutureTask.done()

              當執行 Future.get()方法時,如果 FutureTask不是處於執行完成狀態RAN或已取消狀態CANCELLED,當前執行線程將到AQS的線程等待(如下圖的線程A、B、C、D)。當線程執行FutureTask.run()方法或FutureTask.cancel() 方法時,會喚醒線程等待隊列的第一個線程(線程E喚醒線程A)

在這裏插入圖片描述

              假設開始時FutureTask處於未啓動狀態或已啓動狀態,等待隊列中已經有3個線程(A、B和C)在等待。此時,線程D執行get()方法將導致線程D也到等待隊列中取等待。

              當線程E執行run()方法時,會喚醒隊列中的第一個線程A。線程A喚醒後,首先把自己從等待隊列中刪除,然後喚醒它的後繼線程B,最後線程A從get()方法返回。線程B、C和D重複A線程的處理流程。最終,在隊列中等待的所有線程都被級聯喚醒並從get()方法返回。

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