AsyncTask源碼解析 從AsyncTask講到線程池

我覺得AsyncTask算是Android源碼裏面比較傑出的一個了.裏面涉及的知識點很多,並且運用起來也很合理.


在安卓裏,使用後臺線程,並且需要與主線程交互的方式,最直觀的就是new Thread+Handler 和 AsyncTask..

在new Thread+Hander後,安卓開拓大神又創建了AsyncTask.我覺得有兩個理由.

第一.不用關注創建線程,使用線程,管理線程的邏輯,只需要關注業務的3個方法.任務開始前準備,任務執行,任務完成後的工作

第二.AsyncTask使用Java語言的線程池,或者更廣義來說,使用了java.util.concurrent這個並行編程大禮包.使得多線程操作更高效更得心應手.


AsyncTask關於執行的3個方法.

public final AsyncTask<Params, Progress, Result> execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params);}

public static void execute(Runnable runnable) { sDefaultExecutor.execute(runnable); }

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) {....}

從名字就可以看出,設計到線程池Executor知識.所以不可避免,需要對Executor這個家族瞭解瞭解.



關於Executor


Executor和Executors的關係,類似於Collection和Collections的關係.一個是定義規範的接口,一個工具類.

Executors經典的方法有3個

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
	
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
可以看到這3者都用到了ThreadPoolExecutor這個子類.(其中使用了newSingleThreadExecutor方法使用了子類FinalizableDelegatedExecutorService對ThreadPoolExecutor進行了一層封裝).

這個ThreadPoolExecutor構造方法接受了幾個變量.

第一個corePoolSize表示核心線程數,第二個maximumPoolSize表示最大線程數.第三個和第四個,一個表示數量一個表示單位,聯合起來表示這個線程多久沒使用會被銷燬.最後一個表示任務排隊的機制,需要要好好講一下.


這裏先講一下ThreadPoolExecutor運行和調度的具體流程.

if (當前線程數量 < corePoolSize)

新建線程,並處理請求.

else if (當前線程數量 >= corePoolSize && workQueue未裝滿)

放到我們的workQueue中等待處理,當我們的線程處理完任務,就會從這個queue裏面取出新任務來處理.這個wordQueue也就是我們的構造函數第五個參數.

else if (corePoolSize <= 當前線程數量 < maxumumPoolSize && workQueue裝滿)

新建線程來處理任務.

else 

使用RejectedExecutionHandler來做拒絕處理

還有一點就是當線程數大於corePoolSize && 線程等待時間比keepAliveTime長,就會銷燬線程.



回到Executors的三個方法中.來講一下這3個方法產生出來的線程池有什麼特點.

  • newSingleThreadExecutor表示只有一個線程的線程池

corePoolSize和maximumPoolSize都是1,表示永遠只有1個線程.第三個參數傳0表示永不銷燬.通過一個LinkedBlockingQueue來表示無限長度的隊列.可以塞到內存溢出爲止的任務.

  • newFixedThreadPool表示依靠傳參來產生固定數量的線程池

corePoolSize和maximumPoolSize都是用傳過來的參數固定線程池數量.比如傳了數字10就表示固定只有10個線程.當然起碼要來10個任務才能激活創建這10個線程.其他的同上.

  • newCachedThreadPool表示無界線,同時有多少任務需要執行就產生多少個線程的線程池.
corePoolSize傳0,表示必要時,可以把線程全部銷燬一個不留.maximumPoolSize傳了Interger最大值很直白.保留時間60秒.最關鍵是,用了SynchronousQueue.


SynchronousQueue其實也屬於BlockingQueue的子類.跟LinkedBlockingQueue有兄弟關係.它的特點就是沒有容量,也就是沒有隊列.

詳細一點來說,首先它有兩個方法put和offer來放元素,有兩個方法take/poll來取元素.對於每次放元素操作,都需要等到一次取元素來同時進行.反過來也是,每次取元素操作,都需要等到一次放元素操作同時進行.如果這頭生產者放元素,那頭消費者並沒有取元素,就會一直阻塞.所以根據ThreadPoolExecutor使用流程,沒有隊列相當於塞滿隊列.

所以newCachedThreadPool使用這個SynchronousQueue來實現每次有新任務來,要麼用池子裏的空閒線程,要麼新建線程去處理,而不是排隊等待着被執行.



關於Callable

一句話總結的話,Callable就是帶返回值,還可以拋出異常的Runnable.


不帶返回值,不能拋出異常的Runnable能幹的東西太少了.

所以Java1.5帶來了Callable接口以及使用Callable需要的FutureTask等(其實更準確來說應該帶來了java.util.concurrent包)

關於Callable的用法有兩種,

public class CallableAndFuture {
    public static void main(String[] args) {
        Callable<Integer> callable = new Callable<Integer>() {
            public Integer call() throws Exception {
                return new Random().nextInt(100);
            }
        };
        FutureTask<Integer> future = new FutureTask<Integer>(callable);
        new Thread(future).start();
        try {
            Thread.sleep(5000);// 可能做一些事情
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
1.這種是寫個新建一個FutureTask持有一個Callable,然後新建一個Thread來執行.

可以看到由於Thread只能執行Runnable接口的任務,所以是使用Runnable的子類FutureTask,也不是Thread直接去執行Callable接口.

同時FutureTask也實現了Future,主要代表這個任務有返回值,可以取消,可以觀測到任務是否完成.


public class Test {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        MyCallable task = new MyCallable();
        Future<Integer> result = executor.submit(task);
        executor.shutdown();
        try {
            Thread.sleep(1000);
            System.out.println("task運行結果" + result.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
    static class MyCallable implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            System.out.println("子線程在進行計算");
            Thread.sleep(3000);
            int sum = 0;
            for (int i = 0; i < 100; i++)
                sum += i;
            return sum;
        }
    }
}

2.這種是使用線程池,給線程池提交的一個任務.

可以看到ExecutorService裏面有3個方法

  •     <T> Future<T> submit(Callable<T> task);
  •     <T> Future<T> submit(Runnable task, T result);
  •     Future<?> submit(Runnable task);
所以可以選擇提交一個Callable,也可以選擇提交一個FutureTask.這兩個方式有略小的區別.上面的代碼是拿提交Callable當例子.


Futrure.get()是阻塞方法,如有必要,可以調用get(long timeout,TimeUnit unit)表示最多等待多少時間,時間過完還等不到結果就拋出異常.

更常用的用法是這樣

while (!futureTask.isDone()) {
	try {
		Thread.sleep(500);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
}
V = futureTask.get();
另外Future接口還提供了cancel方法和判斷是否已經取消的isCancelled方法.




關於AsyncTask

回到AsyncTask,感覺可以分爲這幾點

1.AsyncTask中的線程池策略

AsyncTask的源碼,一開頭就用了靜態初始化塊創建了一個線程池,來看看參數

    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    private static final int KEEP_ALIVE = 1;

    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 BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);

    /**
     * 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);

核心線程數是CPU+1個. 最大線程數CPU*2+1. 保持時間1秒. 這個是基於android-23的源碼來定的.比如android-24又會把corePoolSize定成最多4個.

使用了BlockingQueue且最大隊列數量爲128.

使用了自定義的ThreadFactory,目的是將來給新建的線程起名爲"AsyncTask #"方便標識.AtomicInteger確保多線程操作下數據能準確,線程安全.


但是神奇的是,AsyncTask用的並不是這個線程池.而是用的自定義的SerialExecutor

    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);
            }
        }
    }
這個類裏面有一個mTasks隊列,用的是ArrayDeque.看了下源碼知道,這個類其實意思是雙向循環隊列.裏面實現的思路是用了數組,一個int型頭(指針)位置,一個int型尾(指針)位置.初始容量是8,當超過這個大小會使用當前大小X2加大容量.

重寫Executor的executor方法.

裏面會對Runnable裏面的邏輯進行封裝一層,即執行完任務或者報異常都會接着執行scheduleNext方法.

scheduleNext會從隊列裏拿出新任務繼續執行,直到隊列爲空.

然後把這個封裝後的Runnable丟到隊列裏.接着判斷當前有沒有任務在進行,沒有的話開始第一個任務.

其實意思就是實現順序執行任務的意思.所以這個SerialExecutor名字也很符合,Serial表示串行,串行線程池.

回到"神奇的地方",神奇就是在創建了一個多線程池THREAD_POOL_EXECUTOR,卻用來串行執行任務.

比如我丟進去1000個任務,每個處理需要1秒.添加打印出當前線程信息.

    static class MyAsyncTask extends AsyncTask<Void, Void, Void> {

        @Override
        protected Void doInBackground(Void... params) {
            try {
                Thread.sleep(1000L);
                Log.e(TAG, "Thread:" + Thread.currentThread());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return null;
        }
    }

02-02 02:38:22.324 1741-1754/com.yao.mytestproject E/HomeActivity: Thread:Thread[AsyncTask #1,5,main]
02-02 02:38:23.366 1741-1773/com.yao.mytestproject E/HomeActivity: Thread:Thread[AsyncTask #2,5,main]
02-02 02:38:24.408 1741-1791/com.yao.mytestproject E/HomeActivity: Thread:Thread[AsyncTask #3,5,main]
02-02 02:38:25.448 1741-1808/com.yao.mytestproject E/HomeActivity: Thread:Thread[AsyncTask #4,5,main]
02-02 02:38:26.463 1741-1825/com.yao.mytestproject E/HomeActivity: Thread:Thread[AsyncTask #5,5,main]
02-02 02:38:27.504 1741-1825/com.yao.mytestproject E/HomeActivity: Thread:Thread[AsyncTask #5,5,main]
02-02 02:38:28.544 1741-1825/com.yao.mytestproject E/HomeActivity: Thread:Thread[AsyncTask #5,5,main]

出來的log發現剛開始5個任務會讓線程池創建5個新線程執行(因爲coolPoolSize根據cpu數量來定,我這裏是4+1=5),接着就是一直用第5號線程執行任務.空着的4個線程,有那麼點浪費的意思啊.


2.上面的Runnable任務怎麼來的,畢竟我們只是重寫了doInBackgroud方法——關於AsyncTask的構造函數

	public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);

                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                Result result = doInBackground(mParams);
                Binder.flushPendingCommands();
                return postResult(result);
            }
        };

        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                try {
                    postResultIfNotInvoked(get());
                } catch (InterruptedException e) {
                    android.util.Log.w(LOG_TAG, e);
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occurred while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };
    }

	...

    private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }

AsyncTask的構造函數會新建一個WorkerRunnable.它是繼承Callable類型,同時持有一個泛型數組,將來用來存放傳過來的可變參數的.

重寫裏面的call方法,裏面會執行doInBackgroud,得到的result會發送個某個handler.

這只是把執行的流程包裝成一個Callable,並不是立即執行裏面的流程.之後用新建FutureTask持有這個Callable.

前面講過,只有用new FutureTask(Callable)的方式,才能把Callable當成Runnable使用,最終才能交給THREAD_POOL_EXECUTOR去執行execute(Runnable)代碼.


3.關於運行在UI線程的3個函數.onPostExecute.onPreExecute.onProgressUpdate

    private static InternalHandler sHandler;

    private static Handler getHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler();
            }
            return sHandler;
        }
    }
	
	private static class InternalHandler extends Handler {
        public InternalHandler() {
            super(Looper.getMainLooper());
        }

        @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;
            }
        }
    }
前面講執行完doInBackgroud,會把執行結果發送到一個Handler.就是這個靜態的InternalHandler.

getHandler典型的單例獲取方法.InternalHandler用的是Looper.getMainLooper,代表運行在UI線程.

裏面有兩個case,一個是執行處理結果,一個是反饋進度的POST_PROGRESS.

反饋進度時候我們只需要在代碼裏調用publishProgress方法,就能發送消息到這個Handler裏面,最終執行onProgressUpdate


4.最終,回到AsyncTask的執行的開始,execute方法

    @MainThread
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }
	 
    @MainThread
    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            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;
        exec.execute(mFuture);

        return this;
    }

AsyncTask的execute方法執行,並且傳了params參數.會使用默認的串行線程池執行executeOnExecutor方法.

裏面首先判斷這個AsyncTask是否在運行或已經運行完畢.(這也告訴我們,new出來AsyncTask只能運行一次)

然後會執行onPreExecute(此時還在UI線程).然後給我們的任務賦值參數後正式開始執行.




關於"神奇的地方"的猜想

首先傳說安卓1.5是AsyncTask是順序執行的.1.6~2.3.2是多個線程並行執行.3.0後又變成順序執行的.所以可能由於歷史遺留那擁有多個線程的THREAD_POOL_EXECUTOR就保留下來了.

然後畢竟這個線程池的聲明是    public static final Executor THREAD_POOL_EXECUTOR,重點是公開的.

所以我們可以這樣用AsyncTask

executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)   就會使用當前CPU數量的最佳策略來並行得跑任務.

還不滿足.可以使用executeOnExecutor(Executors.newFixedThreadPool(10))這樣的傳參決定容量的池子來跑.

再不滿足可以使用executeOnExecutor(Executors.newCachedThreadPool())不限制並行數量的池子來跑.

再再不滿足,請自定義池子跑吧.


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