Android AsyncTask兩種線程池分析和總結

(一)    前言
在android AsyncTask裏面有兩種線程池供我們調用
1.    THREAD_POOL_EXECUTOR, 異步線程池
2.    SERIAL_EXECUTOR,同步線程池
正如上面名稱描述的那樣,一個是異步線程池,多個任務在線程池中併發執行;還有一個是同步執行的。
默認的話,直接調用execute的話,是使用SERIAL_EXECUTOR
下面的話,會用源代碼的方式來說明這兩種線程池的作用和注意事項。
(二)     THREAD_POOL_EXECUTOR用法舉例
1.    代碼
01 private static int produceTaskMaxNumber = 500;
02         public void dotask(){
03             for (int i = 1; i <= produceTaskMaxNumber; i++){
04                 // 產生一個任務,並將其加入到線程池
05                 String task = "task@ " + i;  
06                 Log.d("Sandy", "put " + task);                    
07                 MyAsyncTask asynct = new MyAsyncTask(task);
08                 asynct.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, 0);   
09             }
10         }
11  
12 static class MyAsyncTask extends AsyncTask<Integer, Integer, Integer>{
13         private static int consumeTaskSleepTime = 2000;
14         // 保存任務所需要的數據
15         private Object threadPoolTaskData;         
16         public MyAsyncTask(String s){
17             threadPoolTaskData = s;
18         }
19    
20         protected Integer doInBackground(Integer... arg0) {
21              Log.d("Sandy", "start .." + threadPoolTaskData
22                      + " thread id: " + Thread.currentThread().getId()
23                      + " thread name: " + Thread.currentThread().getName());
24              try {  
25                 // //便於觀察,等待一段時間
26                 Thread.sleep(consumeTaskSleepTime);
27              }
28              catch (Exception e) {
29                  Log.d("Sandy", "", e);
30              }
31              threadPoolTaskData = null;
32              return 0;
33         }
34     }
2.    使用方法比較簡單,首先創建一個繼承自AsyncTask的MyAsyncTask類,然後調用
1 MyAsyncTask asynct = new MyAsyncTask(task);
2 asynct.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, 0);
就可以了。
3.    上面代碼執行的時候會出錯,導致程序異常終止,如下圖

原因是:


就是因爲我們嘗試添加500個task到AsyncTask.THREAD_POOL_EXECUTOR線程池中,但是它的核心線程是5,隊列容量是128,最大線程數是9。
所以,拋出了這個異常。
那麼,接下來的話,我們會去分析這個異常怎麼出來的。

(三)     THREAD_POOL_EXECUTOR代碼分析
從AsyncTask.THREAD_POOL_EXECUTOR的定義開始分析
1.    代碼路徑
frameworks\base\core\java\android\os\AsyncTask.java
代碼:
01 private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
02 private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
03 private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
04 private static final int KEEP_ALIVE = 1;
05  
06 ....
07 /**
08  * An {@link Executor} that can be used to execute tasks in parallel.
09  */
10  public static final Executor THREAD_POOL_EXECUTOR
11          = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
12                  TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
它的幾個參數CORE_POOL_SIZE, MAXIMUN_POOL_SIZE, 都是根據當前手機的處理器數量進行動態定義的。
那麼,繼續往下面看,看這幾個參數傳進去後是什麼意思。
2.    代碼路徑
\libcore\luni\src\main\java\java\util\concurrent\ThreadPoolExecutor.java
代碼:
01 public ThreadPoolExecutor(int corePoolSize,
02                               int maximumPoolSize,
03                               long keepAliveTime,
04                               TimeUnit unit,
05                               BlockingQueue<Runnable> workQueue,
06                               ThreadFactory threadFactory,
07                               RejectedExecutionHandler handler) {
08         if (corePoolSize < 0 ||
09             maximumPoolSize <= 0 ||
10             maximumPoolSize < corePoolSize ||
11             keepAliveTime < 0)
12             throw new IllegalArgumentException();
13         if (workQueue == null || threadFactory == null || handler == null)
14             throw new NullPointerException();
15         this.corePoolSize = corePoolSize;
16         this.maximumPoolSize = maximumPoolSize;
17         this.workQueue = workQueue;
18         this.keepAliveTime = unit.toNanos(keepAliveTime);
19         this.threadFactory = threadFactory;
20         this.handler = handler;
21 }
22  
23 /**
24   * The default rejected execution handler
25   */
26 private static final RejectedExecutionHandler defaultHandler =
27 new AbortPolicy();
這是ThreadPoolExecutor的構造函數,首先需要明白的是這幾個參數的含義
A.    corePoolSize: 線程池維護線程的最少數量
B.    maximumPoolSize:線程池維護線程的最大數量
C.    keepAliveTime: 線程池維護線程所允許的空閒時間
D.    unit: 線程池維護線程所允許的空閒時間的單位
E.    workQueue: 線程池所使用的緩衝隊列
F.    handler: 線程池對拒絕任務的處理策略

當一個任務通過asynct.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, 0)方法欲添加到線程池時:
如果此時線程池中的數量小於corePoolSize,即使線程池中的線程都處於空閒狀態,也要創建新的線程來處理被添加的任務。
如果此時線程池中的數量等於 corePoolSize,但是緩衝隊列 workQueue未滿,那麼任務被放入緩衝隊列。
如果此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,並且線程池中的數量小於maximumPoolSize,建新的線程來處理被添加的任務。
如果此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,並且線程池中的數量等於maximumPoolSize,那麼通過 handler所指定的策略來處理此任務。

也就是:處理任務的優先級爲:
核心線程corePoolSize、任務隊列workQueue、最大線程maximumPoolSize,如果三者都滿了,使用handler處理被拒絕的任務。

當線程池中的線程數量大於 corePoolSize時,如果某線程空閒時間超過keepAliveTime,線程將被終止。這樣,線程池可以動態的調整池中的線程數。

unit可選的參數爲java.util.concurrent.TimeUnit中的幾個靜態屬性:
NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS。

workQueue是BlockQueue的子類,ArrayBlockingQueue,DelayQueue

handler有四個選擇(這不是android的Handler):
ThreadPoolExecutor.AbortPolicy() – 這個也是AsyncTask.THREAD_POOL_EXECUTOR使用的
拋出java.util.concurrent.RejectedExecutionException異常
ThreadPoolExecutor.CallerRunsPolicy()
重試添加當前的任務,他會自動重複調用execute()方法
ThreadPoolExecutor.DiscardOldestPolicy()
拋棄舊的任務
ThreadPoolExecutor.DiscardPolicy()
拋棄當前的任務

所以,正是我們的AsyncTask.THREAD_POOL_EXECUTOR使用了AbortPolicy()類型的handler,所以纔會拋出異常..

那麼,在把任務添加到AsyncTask.THREAD_POOL_EXECUTOR之後,下面的工作就是由這個線程池來調度線程執行任務了。

(四)     AsyncTask. SERIAL_EXECUTOR
1.    使用方法
AsyncTask. SERIAL_EXECUTOR的使用方法和Async.THREAD_POOL_EXECUTOR差不多。不過正如前面所說,它是默認的Executor,所以可以直接調用,所以可以有兩種調用方法。
1 a.    asynct.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, 0);
2 b.    asynct.execute(0);
效果是一樣的

2.執行流程
代碼路徑:
frameworks\base\core\java\android\os\AsyncTask.java
代碼:
01 public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
02             Params... params) {
03         ...
04         exec.execute(mFuture);
05         ....
06 }
07  
08 private static class SerialExecutor implements Executor {
09         final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
10         Runnable mActive;
11         public synchronized void execute(final Runnable r) {
12             mTasks.offer(new Runnable() {
13                 public void run() {
14                     try {
15                         r.run();
16                     } finally {
17                         scheduleNext();
18                     }
19                 }
20             });
21             if (mActive == null) {
22                 scheduleNext();
23             }
24         }
25  
26         protected synchronized void scheduleNext() {
27             if ((mActive = mTasks.poll()) != null) {
28                 THREAD_POOL_EXECUTOR.execute(mActive);
29             }
30         }
31     }
嗯,它會調用到SerialExecutor.execute(Runnable r)方法
在這個方法裏面,它首先把任務放到mTasks這個集合裏面;然後判斷mActivie是否爲空,再調用scheduleNext ()方法。
mActivie爲null的意思是當前沒有任務在執行,如果mActivie!=null,那麼說明當前有任務正在執行,那麼只要把任務添加到mTasks裏面即可。
因爲任務執行完畢後,會再次調用scheduleNext()方法的,就是
finally {
        scheduleNext();
     }
這樣就形成了一種鏈狀調用結構,只要mTasks裏面還有任務,就會不斷逐一調用,如果後面有任務進來,就只要添加到mTasks裏面即可。
同時,不知道大家注意到沒有,這兩個方法都是synchronized的,這樣,就保證了多線程之間調度問題。
否則肯定會出現問題的,至於什麼問題,大家想想就能明白。

4.    繼續分析scheduleNext()方法
這個方法首先把mTasks裏面的數據取一個出來,然後調用
THREAD_POOL_EXECUTOR.execute(mActive);
我暈,這不就是上面一直在分析的AsyncTask.THREAD_POOL_EXECUTOR麼?
好吧,原來AsyncTask.THREAD_POOL_EXECUTOR和AsyncTask.SERIAL_EXECUTOR的區別就是SERIAL_EXECUTOR在THREAD_POOL_EXECUTOR的基礎上添加了一個mTasks的集合來保證任務順序執行而已...

(五)     總結
說了這麼多,總結下
1.    AsyncTask裏面有THREAD_POOL_EXECUTOR和SERIAL_EXECUTOR兩種方式來異步執行任務;THREAD_POOL_EXECUTOR是異步的,而SERIAL_EXECUTOR任務是順序執行的。
2.    THREAD_POOL_EXECUTOR如果添加的任務過多,沒有及時處理的話,會導致程序崩潰,它的隊列size是128;它的調度規則是核心池大小,隊列大小,以及最大線程數和異常處理Handler來決定的。
3.    SERIAL_EXECUTOR本質是在THREAD_POOL_EXECUTOR的基礎上添加一個mTasks的集合來保證任務的順序執行。
發佈了18 篇原創文章 · 獲贊 24 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章