AsyncTask中線程池調度分析,asynctask線程
尊重原創:http://blog.csdn.net/yuanzeyao/article/details/42583215
在Android中,和非同步執行相關的兩個類就是Handler和AsyncTask,所以Android開發人員對於這兩個類是再熟悉不過了,所以這裡我不是講解AsyncTask怎麼使用,而是想分析一下AsyncTask中線程池的調度過程,然後簡單的介紹一下AsyncTask的源碼以及Android3.0前後,AsyncTask中線程池的區別。
在正式學習AsyncTask中的線程池之前,我們首先回憶一下java中的線程池的內容。就從Executor開始吧,Executor就是一個介面,代碼如下:
public interface Executor { /** * Executes the given command at some time in the future. The command * may execute in a new thread, in a pooled thread, or in the calling * thread, at the discretion of the <tt>Executor</tt> implementation. * * @param command the runnable task * @throws RejectedExecutionException if this task cannot be * accepted for execution. * @throws NullPointerException if command is null */ void execute(Runnable command);}
這個介面的內容非常簡單,僅僅提供了一個execute方法,通過上面的描述,execute方法僅僅負責將任務儲存起來,可能在未來某一個時刻執行(可能在一個新的線程,或者一個線程池中執行)。至於具體是在新的線程中執行還是線上程池中執行就是有實現該介面的類決定的。
我們來分析一下execute這個方法的方法簽名,可以發現有一個Runnable型別參數和會拋出兩個異常:
Runnable:這裡我們就把他當作一個線程吧。
RejectedExecutionException:通過execute方法無法將該任務加入任務隊列就會有該異常。
NullPointerException:就是加入的任務時空的。
通過jdk1.6官方文檔,可以知道,有一個介面繼承了Executor,這個介面就是ExecutorService
public interface ExecutorService extends Executor { /**不再接納新的任務,但是之前的任務會繼續執行*/ void shutdown(); /**不再接納新的任務,並且之前的任務也會終止(但是不一定終止成功)返回還沒有開始的工作清單 */ List<Runnable> shutdownNow(); /** 向任務隊列添加一個任務,並且返回一個Future對象Future對象通過get方法可以拿到返回結果,注意這裡可是Callable對象 */ <T> Future<T> submit(Callable<T> task); /**這個方法也是添加一個任務,不過這裡傳入的是Callable對象,並多了一個傳回值型別參數 */ <T> Future<T> submit(Runnable task, T result);/**添加一個任務*/ Future<?> submit(Runnable task);}這個介面比Executor對了幾個方法,其中幾個比較重要的方法我也已經貼出並給出瞭解釋,這裡出現了幾個新的類型,Callable,Future。
首先來分析一下Callable
/** * A task that returns a result and may throw an exception. * Implementors define a single method with no arguments called * <tt>call</tt>. * * <p>The <tt>Callable</tt> interface is similar to {@link * java.lang.Runnable}, in that both are designed for classes whose * instances are potentially executed by another thread. A * <tt>Runnable</tt>, however, does not return a result and cannot * throw a checked exception. * * <p> The {@link Executors} class contains utility methods to * convert from other common forms to <tt>Callable</tt> classes. * * @see Executor * @since 1.5 * @author Doug Lea * @param <V> the result type of method <tt>call</tt> */public interface Callable<V> { /** * Computes a result, or throws an exception if unable to do so. * * @return computed result * @throws Exception if unable to compute a result */ V call() throws Exception;}
原來Callable也是一個介面,有沒有發現Callable介面和Runnable超像?其實他們的功能也超像的,區別主要如下:
1、Runnable是run方法,而Callable是call方法
2、Runnable中的run方法沒有傳回值,而call方法時有傳回值的
3、Runnable中run方法不會拋異常,而call方法會拋異常
所以如果我們遇到了Callable方法就暫且將它和Runnable同等對待吧。
下面再來看看Future這個類
public interface Future<V> { /** * Attempts to cancel execution of this task. This attempt will * fail if the task has already completed, has already been cancelled, * or could not be cancelled for some other reason. If successful, * and this task has not started when <tt>cancel</tt> is called, * this task should never run. If the task has already started, * then the <tt>mayInterruptIfRunning</tt> parameter determines * whether the thread executing this task should be interrupted in * an attempt to stop the task. * * <p>After this method returns, subsequent calls to {@link #isDone} will * always return <tt>true</tt>. Subsequent calls to {@link #isCancelled} * will always return <tt>true</tt> if this method returned <tt>true</tt>. * * @param mayInterruptIfRunning <tt>true</tt> if the thread executing this * task should be interrupted; otherwise, in-progress tasks are allowed * to complete * @return <tt>false</tt> if the task could not be cancelled, * typically because it has already completed normally; * <tt>true</tt> otherwise */ boolean cancel(boolean mayInterruptIfRunning); /** * Returns <tt>true</tt> if this task was cancelled before it completed * normally. * * @return <tt>true</tt> if this task was cancelled before it completed */ boolean isCancelled(); /** * Returns <tt>true</tt> if this task completed. * * Completion may be due to normal termination, an exception, or * cancellation -- in all of these cases, this method will return * <tt>true</tt>. * * @return <tt>true</tt> if this task completed */ boolean isDone(); /** * Waits if necessary for the computation to complete, and then * retrieves its result. * * @return the computed result * @throws CancellationException if the computation was cancelled * @throws ExecutionException if the computation threw an * exception * @throws InterruptedException if the current thread was interrupted * while waiting */ V get() throws InterruptedException, ExecutionException; /** * Waits if necessary for at most the given time for the computation * to complete, and then retrieves its result, if available. * * @param timeout the maximum time to wait * @param unit the time unit of the timeout argument * @return the computed result * @throws CancellationException if the computation was cancelled * @throws ExecutionException if the computation threw an * exception * @throws InterruptedException if the current thread was interrupted * while waiting * @throws TimeoutException if the wait timed out */ V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;}Future其實也是一個泛型介面,其中每個方法都比較重要我們一一來看:
cancel:取消一個任務,不過當一個任務已經完成時,取消就會失敗,這個方法有一個參數mayInterruptIfRunning,如果傳入的是true,那麼即使你的任務已經開始,那麼也會試圖中斷任務所線上程
isDone:如果一個任務執行完畢,那麼這裡返回true
get:該方法有兩個重載方法,首先看沒有參數的,這個方法時一個同步方法,只到任務成功執行完畢才返回,如果任務取消或者中斷,都會拋出異常,有參數的get方法就是限定了等待時間,如果在指定時間內沒有返回,那麼就不再等待。
由於AsyncTask中涉及到了一個FutureTask這個類,並且這個類和Future有關,所以在這裡一起也看看FutureTask的內容。由於FutureTask的代碼比較多,這裡我先貼出它的簽名
public class FutureTask<V> implements RunnableFuture<V>
這個類有一個done方法由於在AsyncTask中會使用到,所以在這裡說一下
/** * Protected method invoked when this task transitions to state * <tt>isDone</tt> (whether normally or via cancellation). The * default implementation does nothing. Subclasses may override * this method to invoke completion callbacks or perform * bookkeeping. Note that you can query status inside the * implementation of this method to determine whether this task * has been cancelled. */ protected void done() { }
通過注釋,done方法是任務狀態變為isdone時調用,該任務可能正常執行,也可能取消了。其實FutureTask應該是彌補Thread的不足出現的,比如一個非同步線程我們如果想拿到它的傳回值,通常有兩種方法,一種是使用另一個線程輪詢這個線程是否執行完畢,第二個就是使用回調,但是使用FutureTask就很簡單,調用get方法即可。
我們發現這個類實現了一個叫做RunnableFuture的介面,我暫且不看RunnableFuture介面,先看看FutrueTask的幾個建構函式吧
/** * Creates a <tt>FutureTask</tt> that will upon running, execute the * given <tt>Callable</tt>. * * @param callable the callable task * @throws NullPointerException if callable is null */ public FutureTask(Callable<V> callable) { if (callable == null) throw new NullPointerException(); sync = new Sync(callable); } /** * Creates a <tt>FutureTask</tt> that will upon running, execute the * given <tt>Runnable</tt>, and arrange that <tt>get</tt> will return the * given result on successful completion. * * @param runnable the runnable task * @param result the result to return on successful completion. If * you don't need a particular result, consider using * constructions of the form: * <tt>Future<?> f = new FutureTask<Object>(runnable, null)</tt> * @throws NullPointerException if runnable is null */ public FutureTask(Runnable runnable, V result) { sync = new Sync(Executors.callable(runnable, result)); }
主要有兩個建構函式,一個傳入的是Callable,另一個傳入的是Runnable,
下面繼承看RunnableFuture這個介面
public interface RunnableFuture<V> extends Runnable, Future<V> { /** * Sets this Future to the result of its computation * unless it has been cancelled. */ void run();}
這個介面繼承了Runnable和Future介面,也就是說FutureTask這個類具備了線程和Future兩者的特性。
上面是jdk中和線程池關係比較緊密的幾個介面和類,下面我就來看看jdk中的一個具體的線程池類ThreadPoolExecuter,這個類實現了ExecuteService介面,並且在Android3.0系統之前實用的的就是這個線程池
先看看ThreadPoolExecuter的建構函式的簽名:(還有其他的建構函式,這裡我那最簡單的建構函式說明)
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
簡單分析一下幾個參數的意義:
corePoolSize:池中所儲存的線程數,包括空閑線程
maximumPoolSize:池中允許的最大線程數
keepAliveTime:當線程數大於核心時,此為終止前多餘的空閑線程等待新任務的最長時間
unit:keepAliveTime 參數的時間單位
workQueue:執行前用於保持任務的隊列
這裡我們要重點理解corePoolSize,maximumPoolSize,workQueue這三個參數的意義。
當我們向線程池中添加一個任務時
1、如果線程數<corePoolSize,那麼建立一個線程,不管此時是否有線程空閑
2、如果線程數=corePoolSize,如果有空閑線程,那麼空閑線程處理加入的任務,如果沒有空閑線程,那麼加入workQueue中。只到workQueue線程已經滿了,才會建立新的線程來處理新加入的任務,如果此時建立的線程數超過了maximumPoolSize,
那麼就會拋RejectedExecutionException異常。
3、如果線程數>corePoolSize時,那麼說明workQueue已經滿了。
通過以上的描述,說明如果workQueue是一個無界隊列,那麼maximumPoolSize就沒有意義了。
好了,關於線程池的分析就到這裡,我們開始看看AsyncTask的源碼吧。
先看看它的一些比較重要的屬性吧
private static final int CORE_POOL_SIZE = 5; private static final int MAXIMUM_POOL_SIZE = 128; private static final int KEEP_ALIVE = 10; private static final BlockingQueue<Runnable> sWorkQueue = new LinkedBlockingQueue<Runnable>(10); private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); public Thread newThread(Runnable r) { return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); } }; private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
結合上面介紹的線程池相關知識,可以知道如下幾點:
1、AsyncTask中的線程池,儲存的線程數量是5個,數量大於5個的線程如果空閑10s中就會銷毀掉。
2、最大的線程數是128個,任務隊列的容量是10,按照前面的分析,最多可以添加128+10個任務,加入139個任務時,程式就會崩潰
3、線程池是 static的,也就是說所有的AsyncTask公用一個線程池(一個應用之類的AsyncTask)。
下面看看AsyncTask的構造方法
public AsyncTask() { mWorker = new WorkerRunnable<Params, Result>() { public Result call() throws Exception { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); return doInBackground(mParams); } }; mFuture = new FutureTask<Result>(mWorker) { @Override protected void done() { Message message; Result result = null; try { result = get(); } catch (InterruptedException e) { android.util.Log.w(LOG_TAG, e); } catch (ExecutionException e) { throw new RuntimeException("An error occured while executing doInBackground()", e.getCause()); } catch (CancellationException e) { message = sHandler.obtainMessage(MESSAGE_POST_CANCEL, new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null)); message.sendToTarget(); return; } catch (Throwable t) { throw new RuntimeException("An error occured while executing " + "doInBackground()", t); } message = sHandler.obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(AsyncTask.this, result)); message.sendToTarget(); } }; }
這裡出現了一個WorkerRunnable和FutureTask類型,其中FutureTask類型前面已經介紹過,我們先看看看WorkerRunnable是什麼類型
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> { Params[] mParams; }
這裡使用到了泛型,Params,Result是什麼意思相信用過AsyncTask都不陌生,不熟悉的可以查一下,還記得前面我說過什麼嗎,Callable就相當於Runnable,所以我們這裡就把WorkRunnable當作一個線程吧,在AsyncTask的建構函式中,定義了一個匿名內部類,並改寫了call方法(可以理解改寫了run方法),在call方法裡,就是調用了doInBackground方法,原來AsyncTask方法的doInBackground是在這裡調用的,現在明白為什麼doInBackgound是在後台線程執行的吧。
接下來使用mWorker為參數建立了一個FutureTask對象,同樣改寫了done方法,前面我們已經介紹了done方法是在任務狀態變為isdone時調用的,在done方法裡面,我們會通過Future的get方法拿到結果。然後通過Handler將結果發送到UI線程處理,這裡Handler處理的訊息可能是MESSAGE_POST_CANCEL(取消),也可能是MESSAGE_POST_RESULT(正常完畢)。其中Message的obj是一個AsyncTaskResult類型,這個類型就是封裝了當前AsyncTask和返回結果的。
我們啟動一個AsyncTask就是調用execute方法,那麼我們看看execute方法做了什麼吧
public final AsyncTask<Params, Progress, Result> execute(Params... params) { if (mStatus != Status.PENDING) { switch (mStatus) { case RUNNING: throw new IllegalStateException("Cannot execute task:" + " the task is already running."); case FINISHED: throw new IllegalStateException("Cannot execute task:" + " the task has already been executed " + "(a task can be executed only once)"); } } mStatus = Status.RUNNING; onPreExecute(); mWorker.mParams = params; sExecutor.execute(mFuture); return this; }
mStatus預設值就是Status.PENDING,如果mStatus不等於這個值,那麼就會拋出異常,如果等於這個值,那麼就會將mStatus變為Status.RUNNING。然後調用onPreExecute方法,由於execute通常是在主線程執行,所以onPreExecute就是在UI線程中調用的的。onPreExecute執行完畢後將mFuture加入到了線程池,
加入線程池,那麼其實調用的就是mWorker中的call方法,其實調用的就是doInBackground方法,當任務狀態變為isdone時,就會向Handler發送訊息,這個Handler是InternalHandler類型
private static class InternalHandler extends Handler { @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"}) @Override public void handleMessage(Message msg) { AsyncTaskResult result = (AsyncTaskResult) msg.obj; switch (msg.what) { case MESSAGE_POST_RESULT: // There is only one result result.mTask.finish(result.mData[0]); break; case MESSAGE_POST_PROGRESS: result.mTask.onProgressUpdate(result.mData); break; case MESSAGE_POST_CANCEL: result.mTask.onCancelled(); break; } } }
當訊息是MESSAGE_POST_RESULT時,那麼就說明正常返回結果,那麼就調用當前AsyncTask的finish方法
private void finish(Result result) { if (isCancelled()) result = null; onPostExecute(result); mStatus = Status.FINISHED; }在finish中如果AsycnTask已經取消了,那麼將結果設定為null,否則調用onPostExecute並將mStatus設定為FINISH。所以一個AsyncTask只能被執行一次,相同的任務需要多執行多次時,只能建立多次AsyncTask。
當訊息是MESSAGE_POST_CANCEL時,就是調用了AsyncTask的cancel方法時,會出現的情況。MESSAGE_POST_PROGRESS訊息大家可以自己分析。
到這裡2.2中AsyncTask的調度過程也算是分析完了,下面分析4.1中AsyncTask的不同點,其中最大的不同點就是線程池的不一樣
* An {@link Executor} that can be used to execute tasks in parallel. */ public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); /** * An {@link Executor} that executes tasks one at a time in serial * order. This serialization is global to a particular process. */ public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
這個有兩個線程池THREAD_POOL_EXECUTOR和2.2中是一樣的,這個多了一個SERIAL_EXECUTOR線程池。
我們看看execute方法到底用的是哪個線程池
public final AsyncTask<Params, Progress, Result> execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); }
在4.1版本中,execute調用的其實是executeOnExecutor方法完成,並將sDefaultExecutor傳遞進來了
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
到這裡其實我們就知道使用的是SERIAL_EXECUTOR線程池了,這個線程池和之前的線程池有什麼區別嗎,我們看看源碼 就知道了
private static class SerialExecutor implements Executor { final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>(); Runnable mActive; public synchronized void execute(final Runnable r) { mTasks.offer(new Runnable() { public void run() { try { r.run(); } finally { scheduleNext(); } } }); if (mActive == null) { scheduleNext(); } } protected synchronized void scheduleNext() { if ((mActive = mTasks.poll()) != null) { THREAD_POOL_EXECUTOR.execute(mActive); } } }
看了源碼之後,你可以看到其實SERIAL_EXECUTOR並不是一個線程池,它僅僅實現了Executor介面然後改寫了execute方法。在execute方法中將任務隊列加入到了ArrayDeque中儲存,這個隊列是沒有容量限制的,加入任務隊列後,如果mActive為null,那麼就會執行scheduleNext方法,這個方法就是從ArrayDeque中取出一個任務然後放入到THREAD_POOL_EXECUTOR這個線程池了。
所以最大的改變也很容易看出來就是限制了向線程池THREAD_POOL_EXECUTOR中加入任務的速度,只能上個任務執行完畢後才能加入下一個任務。所以相當於是一個線程在工作
所以在3.0後的版本中,我們通常不調用execute方法,而是調用executeonExecutor方法,自己定義自己的線程池,然後傳遞進入。