天天看點

Android使用ThreadPoolExecutor線程池處理異步任務 ExecutorService

轉載:https://blog.csdn.net/u010687392/article/details/49850803

說到線程,我想大家都不陌生,因為在開發時候或多或少都會用到線程,而通常建立線程有兩種方式:

1、繼承Thread類 

2、實作Runnable接口

雖說這兩種方式都可以建立出一個線程,不過它們之間還是有一點差別的,主要差別在于在多線程通路同一資源的情況下,用Runnable接口建立的線程可以處理同一資源,而用Thread類建立的線程則各自獨立處理,各自擁有自己的資源。

是以,在Java中大多數多線程程式都是通過實作Runnable來完成的,而對于Android來說也不例外,當涉及到需要開啟線程去完成某件事時,我們都會這樣寫:

<span style="color:#000000"><code>        <span style="color:#000088 !important">new</span> Thread(<span style="color:#000088 !important">new</span> Runnable() {
            <span style="color:#9b859d !important">@Override</span>
            <span style="color:#000088 !important">public</span> <span style="color:#000088 !important">void</span> <span style="color:#009900 !important">run</span>() {
                <span style="color:#880000 !important"><em>//do sth .</em></span>
            }
        }).start();</code></span>
           

這段代碼建立了一個線程并執行,它在任務結束後GC會自動回收該線程,一切看起來如此美妙,是的,它線上程并發不多的程式中确實不錯,而假如這個程式有很多地方需要開啟大量線程來處理任務,那麼如果還是用上述的方式去建立線程處理的話,那麼将導緻系統的性能表現的非常糟糕,更别說在記憶體有限的移動裝置上,主要的影響如下:

1、線程的建立和銷毀都需要時間,當有大量的線程建立和銷毀時,那麼這些時間的消耗則比較明顯,将導緻性能上的缺失

2、大量的線程建立、執行和銷毀是非常耗cpu和記憶體的,這樣将直接影響系統的吞吐量,導緻性能急劇下降,如果記憶體資源占用的比較多,還很可能造成OOM

3、大量的線程的建立和銷毀很容易導緻GC頻繁的執行,進而發生記憶體抖動現象,而發生了記憶體抖動,對于移動端來說,最大的影響就是造成界面卡頓

而針對上述所描述的問題,解決的辦法歸根到底就是:重用已有的線程,進而減少線程的建立。 

是以這就涉及到線程池(ExecutorService)的概念了,線程池的基本作用就是進行線程的複用,下面将具體介紹線程池的使用

ExecutorService

通過上述分析,我們知道了通過new Thread().start()方式建立線程去處理任務的弊端,而為了解決這些問題,Java為我們提供了ExecutorService線程池來優化和管理線程的使用

使用線程池管理線程的優點

1、線程的建立和銷毀由線程池維護,一個線程在完成任務後并不會立即銷毀,而是由後續的任務複用這個線程,進而減少線程的建立和銷毀,節約系統的開銷

2、線程池旨線上程的複用,這就可以節約我們用以往的方式建立線程和銷毀所消耗的時間,減少線程頻繁排程的開銷,進而節約系統資源,提高系統吞吐量

3、在執行大量異步任務時提高了性能

4、Java内置的一套ExecutorService線程池相關的api,可以更友善的控制線程的最大并發數、線程的定時任務、單線程的順序執行等

ExecutorService簡介

通常來說我們說到線程池第一時間想到的就是它:ExecutorService,它是一個接口,其實如果要從真正意義上來說,它可以叫做線程池的服務,因為它提供了衆多接口api來控制線程池中的線程,而真正意義上的線程池就是:ThreadPoolExecutor,它實作了ExecutorService接口,并封裝了一系列的api使得它具有線程池的特性,其中包括工作隊列、核心線程數、最大線程數等。

線程池:ThreadPoolExecutor

既然線程池就是ThreadPoolExecutor,是以我們要建立一個線程池隻需要new ThreadPoolExecutor(…);就可以建立一個線程池,而如果這樣建立線程池的話,我們需要配置一堆東西,非常麻煩,我們可以看一下它的構造方法就知道了:

<span style="color:#000000"><code>public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {<span style="color:#000088 !important">...</span>}</code></span>
           

是以,官方也不推薦使用這種方法來建立線程池,而是推薦使用Executors的工廠方法來建立線程池,Executors類是官方提供的一個工廠類,它裡面封裝好了衆多功能不一樣的線程池,進而使得我們建立線程池非常的簡便,主要提供了如下五種功能不一樣的線程池:

1、newFixedThreadPool() : 

作用:該方法傳回一個固定線程數量的線程池,該線程池中的線程數量始終不變,即不會再建立新的線程,也不會銷毀已經建立好的線程,自始自終都是那幾個固定的線程在工作,是以該線程池可以控制線程的最大并發數。 

栗子:假如有一個新任務送出時,線程池中如果有空閑的線程則立即使用空閑線程來處理任務,如果沒有,則會把這個新任務存在一個任務隊列中,一旦有線程空閑了,則按FIFO方式處理任務隊列中的任務。

2、newCachedThreadPool() : 

作用:該方法傳回一個可以根據實際情況調整線程池中線程的數量的線程池。即該線程池中的線程數量不确定,是根據實際情況動态調整的。 

栗子:假如該線程池中的所有線程都正在工作,而此時有新任務送出,那麼将會建立新的線程去處理該任務,而此時假如之前有一些線程完成了任務,現在又有新任務送出,那麼将不會建立新線程去處理,而是複用空閑的線程去處理新任務。那麼此時有人有疑問了,那這樣來說該線程池的線程豈不是會越集越多?其實并不會,因為線程池中的線程都有一個“保持活動時間”的參數,通過配置它,如果線程池中的空閑線程的空閑時間超過該“儲存活動時間”則立刻停止該線程,而該線程池預設的“保持活動時間”為60s。

3、newSingleThreadExecutor() : 

作用:該方法傳回一個隻有一個線程的線程池,即每次隻能執行一個線程任務,多餘的任務會儲存到一個任務隊列中,等待這一個線程空閑,當這個線程空閑了再按FIFO方式順序執行任務隊列中的任務。

4、newScheduledThreadPool() : 

作用:該方法傳回一個可以控制線程池内線程定時或周期性執行某任務的線程池。

5、newSingleThreadScheduledExecutor() : 

作用:該方法傳回一個可以控制線程池内線程定時或周期性執行某任務的線程池。隻不過和上面的差別是該線程池大小為1,而上面的可以指定線程池的大小。

好了,寫了一堆來介紹這五種線程池的作用,接下來就是擷取這五種線程池,通過Executors的工廠方法來擷取:

<span style="color:#000000"><code>    ExecutorService fixedThreadPool = Executors<span style="color:#009900 !important">.newFixedThreadPool</span>(<span style="color:#006666 !important">5</span>)<span style="color:#880000 !important"><em>;</em></span>
    ExecutorService singleThreadPool = Executors<span style="color:#009900 !important">.newSingleThreadExecutor</span>()<span style="color:#880000 !important"><em>;</em></span>
    ExecutorService cachedThreadPool = Executors<span style="color:#009900 !important">.newCachedThreadPool</span>()<span style="color:#880000 !important"><em>;</em></span>
    ScheduledExecutorService scheduledThreadPool = Executors<span style="color:#009900 !important">.newScheduledThreadPool</span>(<span style="color:#006666 !important">5</span>)<span style="color:#880000 !important"><em>;</em></span>
    ScheduledExecutorService singleThreadScheduledPool = Executors<span style="color:#009900 !important">.newSingleThreadScheduledExecutor</span>()<span style="color:#880000 !important"><em>;</em></span></code></span>
           

我們可以看到通過Executors的工廠方法來建立線程池極其簡便,其實它的内部還是通過new ThreadPoolExecutor(…)的方式建立線程池的,我們看一下這些工廠方法的内部實作:

<span style="color:#000000"><code>    <span style="color:#000088 !important">public</span> <span style="color:#000088 !important">static</span> ExecutorService <span style="color:#009900 !important">newFixedThreadPool</span>(<span style="color:#000088 !important">int</span> nThreads) {
        <span style="color:#000088 !important">return</span> <span style="color:#000088 !important">new</span> ThreadPoolExecutor(nThreads, nThreads,
                                      <span style="color:#006666 !important">0</span>L, TimeUnit.MILLISECONDS,
                                      <span style="color:#000088 !important">new</span> LinkedBlockingQueue<Runnable>());
    }
    <span style="color:#000088 !important">public</span> <span style="color:#000088 !important">static</span> ExecutorService <span style="color:#009900 !important">newSingleThreadExecutor</span>() {
        <span style="color:#000088 !important">return</span> <span style="color:#000088 !important">new</span> FinalizableDelegatedExecutorService
            (<span style="color:#000088 !important">new</span> ThreadPoolExecutor(<span style="color:#006666 !important">1</span>, <span style="color:#006666 !important">1</span>,
                                    <span style="color:#006666 !important">0</span>L, TimeUnit.MILLISECONDS,
                                    <span style="color:#000088 !important">new</span> LinkedBlockingQueue<Runnable>()));
    }
    <span style="color:#000088 !important">public</span> <span style="color:#000088 !important">static</span> ExecutorService <span style="color:#009900 !important">newCachedThreadPool</span>() {
        <span style="color:#000088 !important">return</span> <span style="color:#000088 !important">new</span> ThreadPoolExecutor(<span style="color:#006666 !important">0</span>, Integer.MAX_VALUE,
                                      <span style="color:#006666 !important">60</span>L, TimeUnit.SECONDS,
                                      <span style="color:#000088 !important">new</span> SynchronousQueue<Runnable>());
    }</code></span>
           

我們可以清楚的看到這些方法的内部實作都是通過建立一個ThreadPoolExecutor對象來建立的,正所謂萬變不離其宗,是以我們要了解線程池還是得了解ThreadPoolExecutor這個線程池類,其中由于和定時任務相關的線程池比較特殊(newScheduledThreadPool()、newSingleThreadScheduledExecutor()),它們建立的線程池内部實作是由ScheduledThreadPoolExecutor這個類實作的,而ScheduledThreadPoolExecutor是繼承于ThreadPoolExecutor擴充而成的,是以本質還是一樣的,隻不過多封裝了一些定時任務相關的api,是以我們主要就是要了解ThreadPoolExecutor,從構造方法開始:

<span style="color:#000000"><code>    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {//<span style="color:#000088 !important">...</span>}</code></span>
           

我們可以看到它構造方法的參數比較多,有七個,下面一一來說明這些參數的作用:

corePoolSize:線程池中的核心線程數量 

maximumPoolSize:線程池中的最大線程數量 

keepAliveTime:這個就是上面說到的“保持活動時間“,上面隻是大概說明了一下它的作用,不過它起作用必須在一個前提下,就是當線程池中的線程數量超過了corePoolSize時,它表示多餘的空閑線程的存活時間,即:多餘的空閑線程在超過keepAliveTime時間内沒有任務的話則被銷毀。而這個主要應用在緩存線程池中 

unit:它是一個枚舉類型,表示keepAliveTime的機關,常用的如:TimeUnit.SECONDS(秒)、TimeUnit.MILLISECONDS(毫秒) 

workQueue:任務隊列,主要用來存儲已經送出但未被執行的任務,不同的線程池采用的排隊政策不一樣,稍後再講 

threadFactory:線程工廠,用來建立線程池中的線程,通常用預設的即可 

handler:通常叫做拒絕政策,1、線上程池已經關閉的情況下 2、任務太多導緻最大線程數和任務隊列已經飽和,無法再接收新的任務 。在上面兩種情況下,隻要滿足其中一種時,在使用execute()來送出新的任務時将會拒絕,而預設的拒絕政策是抛一個RejectedExecutionException異常

上面的參數了解起來都比較簡單,不過workQueue這個任務隊列卻要再次說明一下,它是一個

BlockingQueue<Runnable>

對象,而泛型則限定它是用來存放Runnable對象的,剛剛上面講了,不同的線程池它的任務隊列實作肯定是不一樣的,是以,保證不同線程池有着不同的功能的核心就是這個workQueue的實作了,細心的會發現在剛剛的用來建立線程池的工廠方法中,針對不同的線程池傳入的workQueue也不一樣,下面我總結一下這五種線程池分别用的是什麼BlockingQueue:

1、newFixedThreadPool()—>LinkedBlockingQueue 

2、newSingleThreadExecutor()—>LinkedBlockingQueue 

3、newCachedThreadPool()—>SynchronousQueue 

4、newScheduledThreadPool()—>DelayedWorkQueue 

5、newSingleThreadScheduledExecutor()—>DelayedWorkQueue

這些隊列分别表示:

LinkedBlockingQueue:無界的隊列 

SynchronousQueue:直接送出的隊列 

DelayedWorkQueue:等待隊列

當然實作了BlockingQueue接口的隊列還有:ArrayBlockingQueue(有界的隊列)、PriorityBlockingQueue(優先級隊列)。這些隊列的詳細作用就不多介紹了。

線程池ThreadPoolExecutor的使用

使用線程池,其中涉及到一個極其重要的方法,即:

<span style="color:#000000"><code>execute(Runnable <span style="color:#000088 !important">command</span>) 
</code></span>
           

該方法意為執行給定的任務,該任務處理可能在新的線程、已入池的線程或者正調用的線程,這由ThreadPoolExecutor的實作決定。 

newFixedThreadPool 

建立一個固定線程數量的線程池,示例為:

<span style="color:#000000"><code>    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(<span style="color:#006666 !important">3</span>);
    <span style="color:#000088 !important">for</span> (<span style="color:#000088 !important">int</span> i = <span style="color:#006666 !important">1</span>; i <= <span style="color:#006666 !important">10</span>; i++) {
        <span style="color:#000088 !important">final</span> <span style="color:#000088 !important">int</span> index = i;
        fixedThreadPool.execute(<span style="color:#000088 !important">new</span> Runnable() {
             <span style="color:#9b859d !important">@Override</span>
             <span style="color:#000088 !important">public</span> <span style="color:#000088 !important">void</span> <span style="color:#009900 !important">run</span>() {
                 String threadName = Thread.currentThread().getName();
                 Log.v(<span style="color:#009900 !important">"zxy"</span>, <span style="color:#009900 !important">"線程:"</span>+threadName+<span style="color:#009900 !important">",正在執行第"</span> + index + <span style="color:#009900 !important">"個任務"</span>);
                 <span style="color:#000088 !important">try</span> {
                        Thread.sleep(<span style="color:#006666 !important">2000</span>);
                 } <span style="color:#000088 !important">catch</span> (InterruptedException e) {
                        e.printStackTrace();
                 }
             }
         });
     }</code></span>
           

上述代碼,我們建立了一個線程數為3的固定線程數量的線程池,同理該線程池支援的線程最大并發數也是3,而我模拟了10個任務讓它處理,執行的情況則是首先執行前三個任務,後面7個則依次進入任務隊列進行等待,執行完前三個任務後,再通過FIFO的方式從任務隊列中取任務執行,直到最後任務都執行完畢。 

為了展現出線程的複用,我特地在Log中加上了目前線程的名稱,效果為: 

Android使用ThreadPoolExecutor線程池處理異步任務 ExecutorService

newSingleThreadExecutor 

建立一個隻有一個線程的線程池,每次隻能執行一個線程任務,多餘的任務會儲存到一個任務隊列中,等待線程處理完再依次處理任務隊列中的任務,示例為:

<span style="color:#000000"><code>        ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
        <span style="color:#000088 !important">for</span> (<span style="color:#000088 !important">int</span> i = <span style="color:#006666 !important">1</span>; i <= <span style="color:#006666 !important">10</span>; i++) {
            <span style="color:#000088 !important">final</span> <span style="color:#000088 !important">int</span> index = i;
            singleThreadPool.execute(<span style="color:#000088 !important">new</span> Runnable() {
                <span style="color:#9b859d !important">@Override</span>
                <span style="color:#000088 !important">public</span> <span style="color:#000088 !important">void</span> <span style="color:#009900 !important">run</span>() {
                    String threadName = Thread.currentThread().getName();
                    Log.v(<span style="color:#009900 !important">"zxy"</span>, <span style="color:#009900 !important">"線程:"</span>+threadName+<span style="color:#009900 !important">",正在執行第"</span> + index + <span style="color:#009900 !important">"個任務"</span>);
                    <span style="color:#000088 !important">try</span> {
                        Thread.sleep(<span style="color:#006666 !important">1000</span>);
                    } <span style="color:#000088 !important">catch</span> (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }</code></span>
           

代碼還是差不多,隻不過改了線程池的實作方式,效果我想大家都知道,即依次一個一個的處理任務,而且都是複用一個線程,效果為: 

Android使用ThreadPoolExecutor線程池處理異步任務 ExecutorService

其實我們通過newSingleThreadExecutor()和newFixedThreadPool()的方法發現,建立一個singleThreadExecutorPool實際上就是建立一個核心線程數和最大線程數都為1的fixedThreadPool。 

newCachedThreadPool 

建立一個可以根據實際情況調整線程池中線程的數量的線程池,示例為:

<span style="color:#000000"><code>        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        <span style="color:#000088 !important">for</span> (<span style="color:#000088 !important">int</span> i = <span style="color:#006666 !important">1</span>; i <= <span style="color:#006666 !important">10</span>; i++) {
            <span style="color:#000088 !important">final</span> <span style="color:#000088 !important">int</span> index = i;
            <span style="color:#000088 !important">try</span> {
                Thread.sleep(<span style="color:#006666 !important">1000</span>);
            } <span style="color:#000088 !important">catch</span> (InterruptedException e) {
                e.printStackTrace();
            }
            cachedThreadPool.execute(<span style="color:#000088 !important">new</span> Runnable() {
                <span style="color:#9b859d !important">@Override</span>
                <span style="color:#000088 !important">public</span> <span style="color:#000088 !important">void</span> <span style="color:#009900 !important">run</span>() {
                    String threadName = Thread.currentThread().getName();
                    Log.v(<span style="color:#009900 !important">"zxy"</span>, <span style="color:#009900 !important">"線程:"</span> + threadName + <span style="color:#009900 !important">",正在執行第"</span> + index + <span style="color:#009900 !important">"個任務"</span>);
                    <span style="color:#000088 !important">try</span> {
                        <span style="color:#000088 !important">long</span> time = index * <span style="color:#006666 !important">500</span>;
                        Thread.sleep(time);
                    } <span style="color:#000088 !important">catch</span> (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }</code></span>
           

為了展現該線程池可以自動根據實作情況進行線程的重用,而不是一味的建立新的線程去處理任務,我設定了每隔1s去送出一個新任務,這個新任務執行的時間也是動态變化的,是以,效果為: 

Android使用ThreadPoolExecutor線程池處理異步任務 ExecutorService

newScheduledThreadPool 

建立一個可以定時或者周期性執行任務的線程池,示例為:

<span style="color:#000000"><code>        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(<span style="color:#006666 !important">3</span>);
        <span style="color:#880000 !important"><em>//延遲2秒後執行該任務</em></span>
        scheduledThreadPool.schedule(<span style="color:#000088 !important">new</span> Runnable() {
            <span style="color:#9b859d !important">@Override</span>
            <span style="color:#000088 !important">public</span> <span style="color:#000088 !important">void</span> <span style="color:#009900 !important">run</span>() {

            }
        }, <span style="color:#006666 !important">2</span>, TimeUnit.SECONDS);
        <span style="color:#880000 !important"><em>//延遲1秒後,每隔2秒執行一次該任務</em></span>
        scheduledThreadPool.scheduleAtFixedRate(<span style="color:#000088 !important">new</span> Runnable() {
            <span style="color:#9b859d !important">@Override</span>
            <span style="color:#000088 !important">public</span> <span style="color:#000088 !important">void</span> <span style="color:#009900 !important">run</span>() {

            }
        }, <span style="color:#006666 !important">1</span>, <span style="color:#006666 !important">2</span>, TimeUnit.SECONDS);</code></span>
           

newSingleThreadScheduledExecutor 

建立一個可以定時或者周期性執行任務的線程池,該線程池的線程數為1,示例為:

<span style="color:#000000"><code>        ScheduledExecutorService singleThreadScheduledPool = Executors.newSingleThreadScheduledExecutor();
        <span style="color:#880000 !important"><em>//延遲1秒後,每隔2秒執行一次該任務</em></span>
        singleThreadScheduledPool.scheduleAtFixedRate(<span style="color:#000088 !important">new</span> Runnable() {
            <span style="color:#9b859d !important">@Override</span>
            <span style="color:#000088 !important">public</span> <span style="color:#000088 !important">void</span> <span style="color:#009900 !important">run</span>() {
                String threadName = Thread.currentThread().getName();
                Log.v(<span style="color:#009900 !important">"zxy"</span>, <span style="color:#009900 !important">"線程:"</span> + threadName + <span style="color:#009900 !important">",正在執行"</span>);
            }
        },<span style="color:#006666 !important">1</span>,<span style="color:#006666 !important">2</span>,TimeUnit.SECONDS);</code></span>
           

實際上這個和上面的沒什麼太大差別,隻不過是線程池内線程數量的不同,效果為: 

Android使用ThreadPoolExecutor線程池處理異步任務 ExecutorService

每隔2秒就會執行一次該任務

自定義線程池ThreadPoolExecutor

Java内置隻為我們提供了五種常用的線程池,一般來說這足夠用了,不過有時候我們也可以根據需求來自定義我們自己的線程池,而要自定義不同功能的線程池,上面我們也說了線程池功能的不同歸根到底還是内部的BlockingQueue實作不同,是以,我們要實作我們自己相要的線程池,就必須從BlockingQueue的實作上做手腳,而上面也說了BlockingQueue的實作類有多個,那麼這次我們就選用PriorityBlockingQueue來實作一個功能是按任務的優先級來處理的線程池。

1、首先我們建立一個基于PriorityBlockingQueue實作的線程池,為了測試友善,我這裡把核心線程數量設定為3,如下:

<span style="color:#000000"><code>ExecutorService priorityThreadPool = <span style="color:#000088 !important">new</span> ThreadPoolExecutor(<span style="color:#006666 !important">3</span>,<span style="color:#006666 !important">3</span>,<span style="color:#006666 !important">0L</span>,TimeUnit.SECONDS,<span style="color:#000088 !important">new</span> PriorityBlockingQueue<Runnable>());</code></span>
           

2、然後建立一個實作Runnable接口的類,并向外提供一個抽象方法供我們實作自定義功能,并實作Comparable接口,實作這個接口主要就是進行優先級的比較,代碼如下:

<span style="color:#000000"><code><span style="color:#000088 !important">public</span> <span style="color:#000088 !important">abstract</span> <span style="color:#000088 !important">class</span> PriorityRunnable implements Runnable, Comparable<PriorityRunnable> {
    <span style="color:#000088 !important">private</span> <span style="color:#000088 !important">int</span> priority;

    <span style="color:#000088 !important">public</span> <span style="color:#009900 !important">PriorityRunnable</span>(<span style="color:#000088 !important">int</span> priority) {
        <span style="color:#000088 !important">if</span> (priority < <span style="color:#006666 !important">0</span>)
            <span style="color:#000088 !important">throw</span> <span style="color:#000088 !important">new</span> IllegalArgumentException();
        <span style="color:#000088 !important">this</span>.priority = priority;
    }

    @Override
    <span style="color:#000088 !important">public</span> <span style="color:#000088 !important">int</span> <span style="color:#009900 !important">compareTo</span>(PriorityRunnable another) {
        <span style="color:#000088 !important">int</span> my = <span style="color:#000088 !important">this</span>.getPriority();
        <span style="color:#000088 !important">int</span> other = another.getPriority();
        <span style="color:#000088 !important">return</span> my < other ? <span style="color:#006666 !important">1</span> : my > other ? -<span style="color:#006666 !important">1</span> : <span style="color:#006666 !important">0</span>;
    }

    @Override
    <span style="color:#000088 !important">public</span> <span style="color:#000088 !important">void</span> <span style="color:#009900 !important">run</span>() {
        doSth();
    }

    <span style="color:#000088 !important">public</span> <span style="color:#000088 !important">abstract</span> <span style="color:#000088 !important">void</span> <span style="color:#009900 !important">doSth</span>();

    <span style="color:#000088 !important">public</span> <span style="color:#000088 !important">int</span> <span style="color:#009900 !important">getPriority</span>() {
        <span style="color:#000088 !important">return</span> priority;
    }
}</code></span>
           

3、使用我們自己的PriorityRunnable送出任務,整體代碼如下:

<span style="color:#000000"><code>        ExecutorService priorityThreadPool = <span style="color:#000088 !important">new</span> ThreadPoolExecutor(<span style="color:#006666 !important">3</span>, <span style="color:#006666 !important">3</span>, <span style="color:#006666 !important">0</span>L, TimeUnit.SECONDS, <span style="color:#000088 !important">new</span> PriorityBlockingQueue<Runnable>());
        <span style="color:#000088 !important">for</span> (<span style="color:#000088 !important">int</span> i = <span style="color:#006666 !important">1</span>; i <= <span style="color:#006666 !important">10</span>; i++) {
            <span style="color:#000088 !important">final</span> <span style="color:#000088 !important">int</span> priority = i;
            priorityThreadPool.execute(<span style="color:#000088 !important">new</span> PriorityRunnable(priority) {
                <span style="color:#9b859d !important">@Override</span>
                <span style="color:#000088 !important">public</span> <span style="color:#000088 !important">void</span> <span style="color:#009900 !important">doSth</span>() {
                    String threadName = Thread.currentThread().getName();
                    Log.v(<span style="color:#009900 !important">"zxy"</span>, <span style="color:#009900 !important">"線程:"</span> + threadName + <span style="color:#009900 !important">",正在執行優先級為:"</span> + priority + <span style="color:#009900 !important">"的任務"</span>);
                    <span style="color:#000088 !important">try</span> {
                        Thread.sleep(<span style="color:#006666 !important">2000</span>);
                    } <span style="color:#000088 !important">catch</span> (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }</code></span>
           

測試效果

我們看下剛剛自定義的線程池是否達到了我們想要的功能,即根據任務的優先級進行優先處理任務,效果如下: 

Android使用ThreadPoolExecutor線程池處理異步任務 ExecutorService

可以從執行結果中看出,由于核心線程數設定為3,剛開始時,系統有3個空閑線程,是以無須使用任務隊列,而是直接運作前三個任務,而後面再送出任務時由于目前沒有空閑線程是以加入任務隊列中進行等待,此時,由于我們的任務隊列實作是由PriorityBlockingQueue實作的,是以進行等待的任務會經過優先級判斷,優先級高的放在隊列前面先處理。從效果圖中也可以看到後面的任務是先執行優先級高的任務,然後依次遞減。

優先級線程池的優點

從上面我們可以得知,建立一個優先級線程池非常有用,它可以線上程池中線程數量不足或系統資源緊張時,優先處理我們想要先處理的任務,而優先級低的則放到後面再處理,這極大改善了系統預設線程池以FIFO方式處理任務的不靈活

擴充線程池ThreadPoolExecutor

除了内置的功能外,ThreadPoolExecutor也向外提供了三個接口供我們自己擴充滿足我們需求的線程池,這三個接口分别是:

beforeExecute() - 任務執行前執行的方法 

afterExecute() -任務執行結束後執行的方法 

terminated() -線程池關閉後執行的方法

這三個方法在ThreadPoolExecutor内部都沒有實作

前面兩個方法我們可以在ThreadPoolExecutor内部的runWorker()方法中找到,而runWorker()是ThreadPoolExecutor的内部類Worker實作的方法,Worker它實作了Runnable接口,也正是線程池内處理任務的工作線程,而Worker.runWorker()方法則是處理我們所送出的任務的方法,它會同時被多個線程通路,是以我們看runWorker()方法的實作,由于涉及到多個線程的異步調用,必然是需要使用鎖來處理,而這裡使用的是Lock來實作的,我們來看看runWorker()方法内主要實作: 

Android使用ThreadPoolExecutor線程池處理異步任務 ExecutorService

可以看到在task.run()之前和之後分别調用了beforeExecute和afterExecute方法,并傳入了我們的任務Runnable對象

而terminated()則是在關閉線程池的方法中調用,而關閉線程池有兩個方法,我貼其中一個: 

Android使用ThreadPoolExecutor線程池處理異步任務 ExecutorService

是以,我們要擴充線程池,隻需要重寫這三個方法,并實作我們自己的功能即可,這三個方法分别都會在任務執行前調用、任務執行完成後調用、線程池關閉後調用。 

這裡我驗證一下,繼承自ThreadPoolExecutor 并實作那三個方法:

<span style="color:#000000"><code><span style="color:#000088 !important">public</span> <span style="color:#000088 !important">class</span> <span style="color:#4f4f4f !important">MyThreadPoolExecutor</span> <span style="color:#000088 !important">extends</span> <span style="color:#4f4f4f !important">ThreadPoolExecutor</span> {
    <span style="color:#000088 !important">public</span> <span style="color:#009900 !important">MyThreadPoolExecutor</span>(<span style="color:#000088 !important">int</span> corePoolSize, <span style="color:#000088 !important">int</span> maximumPoolSize, <span style="color:#000088 !important">long</span> keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        <span style="color:#000088 !important">super</span>(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    <span style="color:#9b859d !important">@Override</span>
    <span style="color:#000088 !important">protected</span> <span style="color:#000088 !important">void</span> <span style="color:#009900 !important">beforeExecute</span>(Thread t, Runnable r) {
        <span style="color:#000088 !important">super</span>.beforeExecute(t, r);
        String threadName = t.getName();
        Log.v(<span style="color:#009900 !important">"zxy"</span>, <span style="color:#009900 !important">"線程:"</span> + threadName + <span style="color:#009900 !important">"準備執行任務!"</span>);
    }

    <span style="color:#9b859d !important">@Override</span>
    <span style="color:#000088 !important">protected</span> <span style="color:#000088 !important">void</span> <span style="color:#009900 !important">afterExecute</span>(Runnable r, Throwable t) {
        <span style="color:#000088 !important">super</span>.afterExecute(r, t);
        String threadName = Thread.currentThread().getName();
        Log.v(<span style="color:#009900 !important">"zxy"</span>, <span style="color:#009900 !important">"線程:"</span> + threadName + <span style="color:#009900 !important">"任務執行結束!"</span>);
    }

    <span style="color:#9b859d !important">@Override</span>
    <span style="color:#000088 !important">protected</span> <span style="color:#000088 !important">void</span> <span style="color:#009900 !important">terminated</span>() {
        <span style="color:#000088 !important">super</span>.terminated();
        Log.v(<span style="color:#009900 !important">"zxy"</span>, <span style="color:#009900 !important">"線程池結束!"</span>);
    }
}</code></span>
           

而運作後的結果則是,這正符合剛剛說的:

<span style="color:#000000"><code><span style="color:#006666 !important">11</span><span style="color:#4f4f4f !important">-</span><span style="color:#006666 !important">17</span> <span style="color:#006666 !important">05</span>:<span style="color:#006666 !important">47</span>:<span style="color:#006666 !important">51.184</span> <span style="color:#006666 !important">1602</span><span style="color:#4f4f4f !important">-</span><span style="color:#006666 !important">1619</span><span style="color:#4f4f4f !important">/</span><span style="color:#4f4f4f !important">?</span> V/zxy: 線程:pool<span style="color:#4f4f4f !important">-</span><span style="color:#006666 !important">6</span><span style="color:#98c379">-thread</span><span style="color:#4f4f4f !important">-</span><span style="color:#006666 !important">1</span>準備執行任務!
<span style="color:#006666 !important">11</span><span style="color:#4f4f4f !important">-</span><span style="color:#006666 !important">17</span> <span style="color:#006666 !important">05</span>:<span style="color:#006666 !important">47</span>:<span style="color:#006666 !important">51.184</span> <span style="color:#006666 !important">1602</span><span style="color:#4f4f4f !important">-</span><span style="color:#006666 !important">1619</span><span style="color:#4f4f4f !important">/</span><span style="color:#4f4f4f !important">?</span> V/zxy: 線程:pool<span style="color:#4f4f4f !important">-</span><span style="color:#006666 !important">6</span><span style="color:#98c379">-thread</span><span style="color:#4f4f4f !important">-</span><span style="color:#006666 !important">1</span>正在執行任務!
<span style="color:#006666 !important">11</span><span style="color:#4f4f4f !important">-</span><span style="color:#006666 !important">17</span> <span style="color:#006666 !important">05</span>:<span style="color:#006666 !important">47</span>:<span style="color:#006666 !important">53.184</span> <span style="color:#006666 !important">1602</span><span style="color:#4f4f4f !important">-</span><span style="color:#006666 !important">1619</span><span style="color:#4f4f4f !important">/</span><span style="color:#4f4f4f !important">?</span> V/zxy: 線程:pool<span style="color:#4f4f4f !important">-</span><span style="color:#006666 !important">6</span><span style="color:#98c379">-thread</span><span style="color:#4f4f4f !important">-</span><span style="color:#006666 !important">1</span>任務執行結束!
<span style="color:#006666 !important">11</span><span style="color:#4f4f4f !important">-</span><span style="color:#006666 !important">17</span> <span style="color:#006666 !important">05</span>:<span style="color:#006666 !important">47</span>:<span style="color:#006666 !important">58.896</span> <span style="color:#006666 !important">1602</span><span style="color:#4f4f4f !important">-</span><span style="color:#006666 !important">1619</span><span style="color:#4f4f4f !important">/</span><span style="color:#4f4f4f !important">?</span> V/zxy: 線程池結束!</code></span>
           

是以,在上面我們的優先級線程池的代碼上,我們再擴充一個具有暫停功能的優先級線程池,代碼如下: 

具有暫時功能的線程池:

<span style="color:#000000"><code><span style="color:#000088 !important">public</span> <span style="color:#000088 !important">class</span> <span style="color:#4f4f4f !important">PausableThreadPoolExecutor</span> <span style="color:#000088 !important">extends</span> <span style="color:#4f4f4f !important">ThreadPoolExecutor</span> {
    <span style="color:#000088 !important">private</span> <span style="color:#000088 !important">boolean</span> isPaused;
    <span style="color:#000088 !important">private</span> ReentrantLock pauseLock = <span style="color:#000088 !important">new</span> ReentrantLock();
    <span style="color:#000088 !important">private</span> Condition unpaused = pauseLock.newCondition();

    <span style="color:#000088 !important">public</span> <span style="color:#009900 !important">PausableThreadPoolExecutor</span>(<span style="color:#000088 !important">int</span> corePoolSize, <span style="color:#000088 !important">int</span> maximumPoolSize, <span style="color:#000088 !important">long</span> keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        <span style="color:#000088 !important">super</span>(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    <span style="color:#9b859d !important">@Override</span>
    <span style="color:#000088 !important">protected</span> <span style="color:#000088 !important">void</span> <span style="color:#009900 !important">beforeExecute</span>(Thread t, Runnable r) {
        <span style="color:#000088 !important">super</span>.beforeExecute(t, r);
        pauseLock.lock();
        <span style="color:#000088 !important">try</span> {
            <span style="color:#000088 !important">while</span> (isPaused) unpaused.await();
        } <span style="color:#000088 !important">catch</span> (InterruptedException ie) {
            t.interrupt();
        } <span style="color:#000088 !important">finally</span> {
            pauseLock.unlock();
        }

    }

    <span style="color:#000088 !important">public</span> <span style="color:#000088 !important">void</span> <span style="color:#009900 !important">pause</span>() {
        pauseLock.lock();
        <span style="color:#000088 !important">try</span> {
            isPaused = <span style="color:#000088 !important">true</span>;
        } <span style="color:#000088 !important">finally</span> {
            pauseLock.unlock();
        }
    }

    <span style="color:#000088 !important">public</span> <span style="color:#000088 !important">void</span> <span style="color:#009900 !important">resume</span>() {
        pauseLock.lock();
        <span style="color:#000088 !important">try</span> {
            isPaused = <span style="color:#000088 !important">false</span>;
            unpaused.signalAll();
        } <span style="color:#000088 !important">finally</span> {
            pauseLock.unlock();
        }
    }
}</code></span>
           

然後結合上面的優先級線程池的實作,建立具有暫停功能的優先級線程池:

<span style="color:#000000"><code>        PausableThreadPoolExecutor pausableThreadPoolExecutor = <span style="color:#000088 !important">new</span> PausableThreadPoolExecutor(<span style="color:#006666 !important">1</span>, <span style="color:#006666 !important">1</span>, <span style="color:#006666 !important">0</span>L, TimeUnit.SECONDS, <span style="color:#000088 !important">new</span> PriorityBlockingQueue<Runnable>());
        <span style="color:#000088 !important">for</span> (<span style="color:#000088 !important">int</span> i = <span style="color:#006666 !important">1</span>; i <= <span style="color:#006666 !important">100</span>; i++) {
            <span style="color:#000088 !important">final</span> <span style="color:#000088 !important">int</span> priority = i;
            pausableThreadPoolExecutor.execute(<span style="color:#000088 !important">new</span> PriorityRunnable(priority) {
                <span style="color:#9b859d !important">@Override</span>
                <span style="color:#000088 !important">public</span> <span style="color:#000088 !important">void</span> <span style="color:#009900 !important">doSth</span>() {
                    runOnUiThread(<span style="color:#000088 !important">new</span> Runnable() {
                        <span style="color:#9b859d !important">@Override</span>
                        <span style="color:#000088 !important">public</span> <span style="color:#000088 !important">void</span> <span style="color:#009900 !important">run</span>() {
                            textView.setText(priority + <span style="color:#009900 !important">""</span>);
                        }
                    });
                    <span style="color:#000088 !important">try</span> {
                        Thread.sleep(<span style="color:#006666 !important">1000</span>);
                    } <span style="color:#000088 !important">catch</span> (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }</code></span>
           

這裡我為了示範效果,把這個線程池設為隻有一個線程,然後直接在TextView中顯示目前執行的任務的優先級,然後設定個開關,控制線程池的暫停與開始:

<span style="color:#000000"><code>        <span style="color:#000088 !important">if</span> (isPause) {
            pausableThreadPoolExecutor.<span style="color:#000088 !important">resume</span>();
            isPause = <span style="color:#006666 !important">false</span>;
        } <span style="color:#000088 !important">else</span> {
            pausableThreadPoolExecutor.pause();
            isPause = <span style="color:#006666 !important">true</span>;
        }</code></span>
           

效果為: 

Android使用ThreadPoolExecutor線程池處理異步任務 ExecutorService

從效果上來看,該線程池和優先級線程一樣,而且還多了一個暫停與開始的功能

優化線程池ThreadPoolExecutor

雖說線程池極大改善了系統的性能,不過建立線程池也是需要資源的,是以線程池内線程數量的大小也會影響系統的性能,大了反而浪費資源,小了反而影響系統的吞吐量,是以我們建立線程池需要把握一個度才能合理的發揮它的優點,通常來說我們要考慮的因素有CPU的數量、記憶體的大小、并發請求的數量等因素,按需調整。

通常核心線程數可以設為CPU數量+1,而最大線程數可以設為CPU的數量*2+1。

擷取CPU數量的方法為:

<span style="color:#000000"><code>Runtime<span style="color:#009900 !important">.getRuntime</span>()<span style="color:#009900 !important">.availableProcessors</span>()<span style="color:#880000 !important"><em>;</em></span></code></span>
           

shutdown()和shutdownNow()的差別

關于線程池的停止,ExecutorService為我們提供了兩個方法:shutdown和shutdownNow,這兩個方法各有不同,可以根據實際需求友善的運用,如下:

1、shutdown()方法在終止前允許執行以前送出的任務。 

2、shutdownNow()方法則是阻止正在任務隊列中等待任務的啟動并試圖停止目前正在執行的任務。

關于AsyncTask的實作

大家都知道AsyncTask内部實作其實就是Thread+Handler。其中Handler是為了處理線程之間的通信,而這個Thread到底是指什麼呢?通過AsyncTask源碼可以得知,其實這個Thread是線程池,AsyncTask内部實作了兩個線程池,分别是:串行線程池和固定線程數量的線程池。而這個固定線程數量則是通過CPU的數量決定的。

在預設情況下,我們大都通過AsyncTask::execute()來執行任務的, 

,而execute()内部則是調用executeOnExecutor(sDefaultExecutor, params)方法執行的,第一個參數就是指定處理該任務的線程池,而預設情況下AsyncTask是傳入串行線程池(在這裡不講版本的變化),也就是任務隻能單個的按順序執行,而我們要是想讓AsyncTask并行的處理任務,大家都知道調用AsyncTask::executeOnExecutor(sDefaultExecutor, params)方法傳入這個參數即可:AsyncTask.THREAD_POOL_EXECUTOR。 

而這個參數的意義在于為任務指定了一個固定線程數量的線程池去處理,進而達到了并行處理的功能,我們可以在源碼中看到AsyncTask.THREAD_POOL_EXECUTOR這個參數就是一個固定線程數量的線程池:

<span style="color:#000000"><code>    <span style="color:#000088 !important">public</span> <span style="color:#000088 !important">static</span> <span style="color:#000088 !important">final</span> Executor THREAD_POOL_EXECUTOR
            = <span style="color:#000088 !important">new</span> ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);</code></span>
           

繼續閱讀