(一) 前言
在android AsyncTask裏面有兩種線程池供我們調用
1. THREAD_POOL_EXECUTOR, 異步線程池
2. SERIAL_EXECUTOR,同步線程池
正如上面名稱描述的那樣,一個是異步線程池,多個任務在線程池中併發執行;還有一個是同步執行的。
默認的話,直接調用execute的話,是使用SERIAL_EXECUTOR
下面的話,會用源代碼的方式來說明這兩種線程池的作用和注意事項。
(二) THREAD_POOL_EXECUTOR用法舉例
1. 代碼
01 |
private static
int produceTaskMaxNumber =
500 ; |
03 |
for
( int
i = 1 ; i <= produceTaskMaxNumber; i++){
|
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 ); |
12 |
static class
MyAsyncTask extends
AsyncTask<Integer, Integer, Integer>{ |
13 |
private
static int
consumeTaskSleepTime = 2000 ;
|
15 |
private
Object threadPoolTaskData; |
16 |
public
MyAsyncTask(String s){ |
17 |
threadPoolTaskData = s; |
20 |
protected
Integer doInBackground(Integer... arg0) { |
21 |
Log.d( "Sandy" ,
"start .." + threadPoolTaskData |
22 |
+
" thread id: " + Thread.currentThread().getId() |
23 |
+
" thread name: " + Thread.currentThread().getName());
|
26 |
Thread.sleep(consumeTaskSleepTime);
|
29 |
Log.d( "Sandy" ,
"" , e); |
31 |
threadPoolTaskData =
null ; |
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 ; |
08 |
* An {@link Executor} that can be used to execute tasks in parallel. |
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, |
05 |
BlockingQueue<Runnable> workQueue, |
06 |
ThreadFactory threadFactory, |
07 |
RejectedExecutionHandler handler) { |
08 |
if
(corePoolSize < 0
|| |
09 |
maximumPoolSize <=
0 || |
10 |
maximumPoolSize < corePoolSize || |
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; |
24 |
* The default rejected execution handler |
26 |
private static
final RejectedExecutionHandler defaultHandler = |
這是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.執行流程
代碼路徑:
frameworks\base\core\java\android\os\AsyncTask.java
代碼:
01 |
public final
AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, |
04 |
exec.execute(mFuture); |
08 |
private static
class SerialExecutor implements
Executor { |
09 |
final
ArrayDeque<Runnable> mTasks = new
ArrayDeque<Runnable>(); |
11 |
public
synchronized void
execute( final Runnable r) { |
12 |
mTasks.offer( new
Runnable() { |
21 |
if
(mActive == null ) { |
26 |
protected
synchronized void
scheduleNext() { |
27 |
if
((mActive = mTasks.poll()) != null ) { |
28 |
THREAD_POOL_EXECUTOR.execute(mActive); |
嗯,它會調用到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的集合來保證任務的順序執行。