Executor框架整理(一)

前言

在Java中,使用線程來異步執行任務。
Java線程的創建與銷燬需要一定的開銷,如果我們爲每一個任務創建一個新線程來執行,這些線程的創建與銷燬將消耗大量的計算資源。
同時,爲每一個任務創建一個新線程來執行,這種策略可能會使處於高負荷狀態的應用最終崩潰。

一、什麼是Executor框架?

我們知道線程池就是線程的集合,線程池集中管理線程,以實現線程的重用,降低資源消耗,提高響應速度等。線程用於執行異步任務,單個的線程既是工作單元也是執行機制,從JDK1.5開始,爲了把工作單元與執行機制分離開,Executor框架誕生了,他是一個用於統一創建與運行的接口。Executor框架實現的就是線程池的功能
二、Executor框架結構

1、Executor框架包括3大部分:

(1)任務。也就是工作單元,包括被執行任務需要實現的接口:Runnable接口或者Callable接口;

(2)任務的執行。也就是把任務分派給多個線程的執行機制,包括Executor接口及繼承自Executor接口的ExecutorService接口。Executor框架有兩個關鍵類實現了ExecutorService接口(ThreadPoolExecutorScheduledThreadPoolExecutor)。

(3)異步計算的結果。包括Future接口及實現了Future接口的FutureTask類。

Executor框架的成員及其關係可以用一下的關係圖表示

下面是這些類和接口的簡介:

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

2、Executor框架的使用示意圖:

使用步驟:

(1)創建Runnable並重寫run()方法或者Callable對象並重寫call()方法:

class callableTest implements Callable<String >{
            @Override
            public String call() {
                try{
                    String a = "return String";
                    return a;
                }
                catch(Exception e){
                    e.printStackTrace();
                    return "exception";
                }
            }
        }

(2)創建Executor接口的實現類ThreadPoolExecutor類或者ScheduledThreadPoolExecutor類的對象,然後調用其execute()方法或者submit()方法把工作任務添加到線程中,如果有返回值則返回Future對象。其中Callable對象有返回值,因此使用submit()方法;而Runnable可以使用execute()方法,此外還可以使用submit()方法,只要使用callable(Runnable task)或者callable(Runnable task,  Object result)方法把Runnable對象包裝起來就可以,使用callable(Runnable task)方法返回的null,使用callable(Runnable task,  Object result)方法返回result。

ThreadPoolExecutor tpe = new ThreadPoolExecutor(5, 10,
                100, MILLISECONDS, new ArrayBlockingQueue<Runnable>(5));
Future<String> future = tpe.submit(new callableTest());

(3)調用Future對象的get()方法後的返回值,或者調用Future對象的cancel()方法取消當前線程的執行。最後關閉線程池

三、Executor框架成員:ThreadPoolExecutor實現類、ScheduledThreadPoolExecutor實現類、Future接口、Runnable和Callable接口、Executors工廠類

(一). ThreadPoolExecutor

是Executor接口的一個重要的實現類,是線程池的具體實現,用來執行被提交的任務。

通常使用工廠類Executors來創建。Executors可以創建3種類型的ThreadPoolExecutor

1.  FixedThreadPool:適用於爲了滿足資源管理的需求,而需要限制當前線程數量的應用場景,它適用於負載比較重的服務器。newFixedThreadPool創建一個可重用固定線程數的線程池,以共享的無界隊列方式來運行這些線程。

ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 20; i++) {
            Runnable syncRunnable = new Runnable() {
                @Override
                public void run() {
                    Log.e(TAG, Thread.currentThread().getName());
                }
            };
            executorService.execute(syncRunnable);
        }

運行結果:總共只會創建5個線程, 開始執行五個線程,當五個線程都處於活動狀態,再次提交的任務都會加入隊列等到其他線程運行結束,當線程處於空閒狀態時會被下一個任務複用

下面是Executors提供的,創建使用固定線程數的FixedThreadPool的API:

public static ExecutorService newFixedThreadPool(int var0) {
    return new ThreadPoolExecutor(var0, var0, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
}
public static ExecutorService newFixedThreadPool(int var0, ThreadFactory var1) {
    return new ThreadPoolExecutor(var0, var0, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), var1);
}

FixedThreadPoolcorePoolSizemaximumPoolSize都被設置爲創建FixedThreadPool時指定的參數nThreads

當線程池中的線程數大於corePoolSize時,keepAliveTime爲多餘的空閒線程等待新任務的最長時間,超過這個時間後多餘的線程將被終止。這裏把keepAliveTime設置爲0L,意味着多餘的空閒線程會被立即終止。

  • 圖中1:如果當前運行的線程數少於corePoolSize,則創建新線程來執行任務。
  • 圖中2:在線程池完成預熱之後(當前運行的線程數等於corePoolSize),將任務加入LinkedBlockingQueue。
  • 圖中3:線程執行完1中的任務後,會在循環中反覆從LinkedBlockingQueue獲取任務來執行。

FixedThreadPool使用無界隊列LinkedBlockingQueue作爲線程池的工作隊列(隊列的容量爲Integer.MAX_VALUE)。
使用無界隊列作爲工作隊列會對線程池帶來如下影響:

  •     當線程池中的線程數達到corePoolSize後,新任務將在無界隊列中等待,因此線程池中的線程數不會超過corePoolSize。
  •     由於上一點,使用無界隊列時maximumPoolSize將是一個無效參數。
  •     由於前面兩點,使用無界隊列時keepAliveTime將是一個無效參數。
  •     由於使用無界隊列,運行中的FixedThreadPool(未執行方法shutdown()或shutdownNow())不會拒絕任務(不會調用RejectedExecutionHandler.rejectedExecution方法)。

FixedThreadPool的工作流程大致如下:

  • 當前核心線程池總線程數量小於corePoolSize,那麼創建線程並執行任務;
  • 如果當前線程數量等於corePoolSize,那麼把 任務添加到阻塞隊列中;
  • 如果線程池中的線程執行完任務,那麼獲取阻塞隊列中的任務並執行;

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

創建一個單線程化的Executor。它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行

ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 20; i++) {
            Runnable syncRunnable = new Runnable() {
                @Override
                public void run() {
                    Log.e(TAG, Thread.currentThread().getName());
                }
            };
            executorService.execute(syncRunnable);
        }

 運行結果:只會創建一個線程,當上一個執行完之後纔會執行第二個

下面是Executors提供的,創建使用單個線程的SingleThreadExecutor的API。

public static ExecutorService newSingleThreadExecutor() {
    return new Executors.FinalizableDelegatedExecutorService(
            new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue())
    );
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory var0) {
    return new Executors.FinalizableDelegatedExecutorService(
            new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), var0)
    );
}

注意: SingleThreadExecutor的corePoolSize和maximumPoolSize被設置爲1。其他參數與FixedThreadPool相同。      SingleThreadExecutor使用無界隊列LinkedBlockingQueue作爲線程池的工作隊列(隊列的容量爲Integer.MAX_VALUE)。SingleThreadExecutor使用無界隊列作爲工作隊列對線程池帶來的影響與FixedThreadPool相同,這裏就不贅述了。

SingleThreadPool的工作流程大概如下:

  • 當前核心線程池總線程數量小於corePoolSize(1),那麼創建線程並執行任務;
  • 如果當前線程數量等於corePoolSize,那麼把 任務添加到阻塞隊列中;
  • 如果線程池中的線程執行完任務,那麼獲取阻塞隊列中的任務並執行;

3. CachedThreadPool:是大小無界的線程池,適用於執行很多的短期異步任務的小程序,或者是負載較輕的服務器。是一個會根據需要創建新線程的線程池。newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程

下面是Executors提供的,創建一個會根據需要創建新線程的CachedThreadPool的API。

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue());
}
public static ExecutorService newCachedThreadPool(ThreadFactory var0) {
    return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue(), var0);
}

CachedThreadPool的corePoolSize被設置爲0,即corePool爲空;maximumPoolSize被設置爲Integer.MAX_VALUE,即maximumPool是無界的。這裏把keepAliveTime設置爲60L,意味着CachedThreadPool中的空閒線程等待新任務的最長時間爲60秒,空閒線程超過60秒後將會被終止。
FixedThreadPool和SingleThreadExecutor使用無界隊列LinkedBlockingQueue作爲線程池的工作隊列。
CachedThreadPool使用沒有容量的SynchronousQueue作爲線程池的工作隊列,但CachedThreadPool的maximumPool是無界的。
這意味着,如果主線程提交任務的速度高於maximumPool中線程處理任務的速度時,CachedThreadPool會不斷創建新線程。
極端情況下,CachedThreadPool會因爲創建過多線程而耗盡CPU和內存資源。

註釋: 上圖1: 首先執行SynchronousQueue.offer(Runnable task)。如果當前maximumPool中有空閒線程正在執行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那麼主線程執行offer操作與空閒線程執行的poll操作配對成功,主線程把任務交給空閒線程執行,execute()方法執行完成;否則執行下面的步驟2。
         上圖2:當初始maximumPool爲空,或者maximumPool中當前沒有空閒線程時,將沒有線程執行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。這種情況下,步驟1將失敗。此時CachedThreadPool會創建一個新線程執行任務,execute()方法執行完成。
       上圖3:.在步驟2中新創建的線程將任務執行完後,會執行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。這個poll操作會讓空閒線程最多在SynchronousQueue中等待60秒鐘。如果60秒鐘內主線程提交了一個新任務(主線程執行步驟1),那麼這個空閒線程將執行主線程提交的新任務;否則,這個空閒線程將終止。由於空閒60秒的空閒線程會被終止,因此長時間保持空閒的CachedThreadPool不會使用任何資源。
前面提到過,SynchronousQueue是一個沒有容量的阻塞隊列。每個插入操作必須等待另一個線程的對應移除操作,反之亦然。CachedThreadPool使用SynchronousQueue,把主線程提交的任務傳遞給空閒線程執行。

ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 100; i++) {
            Runnable syncRunnable = new Runnable() {
                @Override
                public void run() {
                    Log.e(TAG, Thread.currentThread().getName());
                }
            };
            executorService.execute(syncRunnable);
        }

運行結果:可以看出緩存線程池大小是不定值,可以需要創建不同數量的線程,在使用緩存型池時,先查看池中有沒有以前創建的線程,如果有,就複用.如果沒有,就新建新的線程加入池中,緩存型池子通常用於執行一些生存期很短的異步型任務

CachedThreadPool的工作流程大概如下:

    首先執行SynchronizedQueue.offer(  )把任務提交給阻塞隊列,如果這時候正好在線程池中有空閒的線程執行SynchronizedQueue.poll( ),那麼offer操作和poll操作配對,線程執行任務;
    如果執行SynchronizedQueue.offer(  )把任務提交給阻塞隊列時maximumPoolSize=0.或者沒有空閒線程來執行SynchronizedQueue.poll( ),那麼步驟1失敗,那麼創建一個新線程來執行任務;
    如果當前線程執行完任務則循環從阻塞隊列中獲取任務,如果當前隊列中沒有提交(offer)任務,那麼線程等待keepAliveTime時間,在CacheThreadPool中爲60秒,在keepAliveTime時間內如果有任務提交則獲取並執行任務,如果沒有則銷燬線程,因此最後如果一直沒有任務提交了,線程池中的線程數量最終爲0。


(二)ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor類繼承了ThreadPoolExecutor並實現了ScheduledExecutorService接口。主要用於在給定的延遲後執行任務或者定期執行任務。作用類似於java.util包下的Timer類,但是比Timer功能更強大、更靈活,因爲Timer只能控制單個線程延遲或定期執行,而ScheduledThreadPoolExecutor對應的是多個線程的後臺線程。

Executors可以創建2種類型的ScheduledThreadPoolExecutor,如下:

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

下面是工廠類Executors提供的,創建 固定個數 線程的ScheduledThreadPoolExecutor的API。

public static ScheduledExecutorService newScheduledThreadPool(int var0) {
    return new ScheduledThreadPoolExecutor(var0);
}
public static ScheduledExecutorService newScheduledThreadPool(int var0, ThreadFactory var1) {
    return new ScheduledThreadPoolExecutor(var0, var1);
}

 2. SingleThreadScheduledExecutor:適用於需要單個線程延時或者定期的執行任務,同時需要保證各個任務順序執行的應用場景。

ScheduledExecutorService stse = Executors.newSingleThreadScheduledExecutor(int threadNums);
ScheduledExecutorService stp = Executors.newSingleThreadScheduledExecutor(int threadNums, ThreadFactory threadFactory);

 

ScheduledThreadPoolExecutor的運行機制

DelayQueue是一個無界隊列,所以ThreadPoolExecutormaximumPoolSizeScheduledThreadPoolExecutor中沒有什麼意義(設置maximumPoolSize的大小沒有什麼效果)。

ScheduledThreadPoolExecutor的執行主要分爲兩大部分。

  •     當調用ScheduledThreadPoolExecutor的scheduleAtFixedRate()方法或者scheduleWithFixedDelay()方法時,會向ScheduledThreadPoolExecutor的DelayQueue添加一個實現了RunnableScheduledFutur接口的ScheduledFutureTask。
  •     線程池中的線程從DelayQueue中獲取ScheduledFutureTask,然後執行任務。

ScheduledThreadPoolExecutor爲了實現週期性的執行任務,對ThreadPoolExecutor做了如下的修改。

  •     使用DelayQueue作爲任務隊列。
  •     獲取任務的方式不同(後文會說明)。
  •     執行週期任務後,增加了額外的處理(後文會說明)

(1)ScheduledThreadPoolExecutor的實現

通過查看源碼,可以發現ScheduledThreadPoolExecutor的實現主要是通過把任務封裝爲ScheduledFutureTask來實現。ScheduledThreadPoolExecutor通過它的scheduledAtFixedTime()方法或者scheduledWithFixedDelay()方法向阻塞隊列添加一個實現了RunnableScheduledFutureTask接口的ScheduledFutureTask類對象。

ScheduledFutureTask主要包含3個成員變量,如下。

  •     long型成員變量time,表示這個任務將要被執行的具體時間。
  •     long型成員變量sequenceNumber,表示這個任務被添加到ScheduledThreadPoolExecutor中的序號。
  •     long型成員變量period,表示任務執行的間隔週期。

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

下面是對這4個步驟的說明:

    上圖1: 線程·1從DelayQueue中獲取已到期的ScheduledFutureTask 的 DelayQueue.take()。到期任務是指ScheduledFutureTask的time大於等於當前時間。
    上圖2: 線程1執行這個ScheduledFutureTask。
    上圖3: 線程1修改ScheduledFutureTask的time變量爲下次將要被執行的時間。
    上圖4: 線程1把這個修改time之後的ScheduledFutureTask放回DelayQueue中DelayQueue.add()。
 

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