多線程,從解剖AsyncTask開始

常見使用方式

這里我以圖片尺寸壓縮任務來舉個栗子:

    /**
     * 壓縮
     *
     * @param fileInputPath    需要壓縮圖片SD卡路徑
     * @param compressCallback 壓縮結果回調
     */
    @MainThread
    public void bitmapCompress(String fileInputPath, BitmapCompressCallback compressCallback) {
        // BitmapCropTask繼承并實現了AsyncTask的抽象方法doInBackground,執行異步任務進行壓縮,壓縮完回調compressCallback
        new BitmapCropTask(compressCallback).execute(fileInputPath);
    }

AsyncTask源碼分析

public abstract class AsyncTask<Params, Progress, Result> {
    ......

    @WorkerThread
    protected abstract Result doInBackground(Params... params);

    ......
}

我們發現AsyncTask是一個泛型類,三個參數分別是:

  • Params:doInBackground方法的入參,它是可變參數;
  • Progress:AsyncTask所執行后臺任務的進度;
  • Result:后臺任務的返回結果。

同時也是一個抽象類,具體業務需要繼承AsyncTask并實現其doInBackground方法

首先我們看看構造器中發生了什么,

    private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
        Params[] mParams;
    }
    private final WorkerRunnable<Params, Result> mWorker;
    private final FutureTask<Result> mFuture;
    /**
     * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
     */
    public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);// 任務開始執行標記,原子操作
                Result result = null;
                try {
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);// 設置線程的優先級
                    //noinspection unchecked
                    result = doInBackground(mParams);// 任務執行返回result
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);// 任務執行過程發生異常,標記任務為取消,原子操作
                    throw tr;
                } finally {
                    postResult(result);// 最終將執行結果post到Handler中,傳遞到目標主線程中
                }
                return result;// 后臺任務執行的結果
            }
        };

        mFuture = new FutureTask<Result>(mWorker) {// 將后臺任務包裝到FutureTask中
            /**
             * 任務結束時(正常執行結束或者取消),該回調函數會被觸發
             */
            @Override
            protected void done() {
                try {
                    postResultIfNotInvoked(get());// 調用get方法阻塞當前線程直到獲取到任務執行結果
                } catch (InterruptedException e) {// 如果任務線程執行時恰好cancel,即mayInterruptIfRunning為true
                    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);
                }
            }
        };
    }

我們注意到,構造器中實例化了兩個對象,WorkerRunnable(Callable)對象和FutureTask對象。熟悉Java并發編程的人應該都知道這兩種類型,下面具體分析:

Callable

Callable是類似于Runnable的接口,實現Callable接口的類和實現Runnable的類都是可被其他線程執行的任務

Callable的接口定義如下:

public interface Callable<V> { 
      V call() throws Exception; 
} 

Callable和Runnable的區別如下:

  • Callable定義的方法是call,而Runnable定義的方法是run。

  • Callable的call方法可以有返回值,而Runnable的run方法不能有返回值。

  • Callable的call方法可拋出異常,而Runnable的run方法不能拋出異常。

FutureTask

public class FutureTask<V> implements RunnableFuture<V> {

    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;// 對Callable對象的包裝
        this.state = NEW;       // ensure visibility of callable
    }
    
    public void run() {
        if (state != NEW ||
            !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();// c就是被包裝進FutureTask的mWorker對象,就是通過調起mWorker.call方法開始執行任務
                    ran = true;
                } catch (Throwable ex) {// 后臺任務內部執行異常
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);// 設置后臺任務執行結果
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

FutureTask實現的RunnableFuture接口,RunnableFuture兼具Runnable規范Future規范既可以執行任務,也可以獲取任務的執行結果,支持取消任務。FutureTask幫助我們實現了具體的任務執行以及和Future接口中的get方法的關聯。

任務執行

    @MainThread
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        // 應該可以看到,不管上層有多少個AsyncTask執行execute,任務的執行都是落到一個全局靜態的sDefaultExecutor去執行的
        return executeOnExecutor(sDefaultExecutor, params);
    }

execute方法內部調用了executeOnExecutor方法,并傳入了一個sDefaultExecutor以及任務的入參,那么注意到這個靜態全局sDefaultExecutor又是什么呢?

SerialExecutor

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
private static class SerialExecutor implements Executor {
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();// 任務緩存隊列,基于ArrayDeque,線程不安全,但速度快于LinkedList
        Runnable mActive;// 當前執行的任務對象

        /**
          * 這里需要做同步控制,因為上層AsyncTask不明確是在什么線程中開始執行execute,因為緩存任務的ArrayDeque不是線程安全的,所以這個共享資源的訪問需要做同步互斥的控制,包括后面的scheduleNext也是一樣,因為任務的調度是在上一個任務執行的線程中開始調度
          */
        public synchronized void execute(final Runnable r) {
            mTasks.offer(new Runnable() {// 將任務添加到ArrayDeque類型的任務隊列中
                public void run() {
                    try {
                        r.run();// 這里的Runnable就是mFuture
                    } finally {
                        // 當前任務在相應線程執行完成之后,開始調度其之后入隊的任務開始運行
                        scheduleNext();
                    }
                }
            });

            // 任務隊列里的任務尚未開始被執行,則從此開始消耗任務隊列
            if (mActive == null) {
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {// 一直從隊列頭部獲取任務,不為空就通過線程池去執行任務
                THREAD_POOL_EXECUTOR.execute(mActive);// 交給自定義線程池去執行
            }
        }
    }

SerialExecutor是一個多任務串行執行器,從ActivityThread源碼中可以發現SERIAL_EXECUTOR 是Android3.1以后的默認任務執行器。SerialExecutor主要用于任務的排隊,做到了先進先出的順序;而真正的任務執行是THREAD_POOL_EXECUTOR。

ActivityThread.java

        // If the app is Honeycomb MR1 or earlier, switch its AsyncTask
        // implementation to use the pool executor.  Normally, we use the
        // serialized executor as the default. This has to happen in the
        // main thread so the main looper is set right.
        if (data.appInfo.targetSdkVersion <= android.os.Build.VERSION_CODES.HONEYCOMB_MR1) {
            AsyncTask.setDefaultExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
        }

THREAD_POOL_EXECUTOR

這個自定義的線程池配置參數如下:

    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();// 可用CPU數量
    // We want at least 2 threads and at most 4 threads in the core pool,
    // preferring to have 1 less than the CPU count to avoid saturating
    // the CPU with background work
    private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));// 核心線程數量
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;// 線程池大小
    private static final int KEEP_ALIVE_SECONDS = 30;// 閑置時間

    // 線程創建工廠
    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);

    public static final Executor THREAD_POOL_EXECUTOR;
    static {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
        threadPoolExecutor.allowCoreThreadTimeOut(true);// 允許核心線程在閑置時間到達后釋放
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }

Android3.1及以下的系統版本,多個AsyncTask任務是通過THREAD_POOL_EXECUTOR并發執行的。繼續深挖

     // AsyncTask執行的三種狀態 
     public enum Status {
        /**
         * Indicates that the task has not been executed yet.
         */
        PENDING,// 掛起
        /**
         * Indicates that the task is running.
         */
        RUNNING,// 執行
        /**
         * Indicates that {@link AsyncTask#onPostExecute} has finished.
         */
        FINISHED,// 執行結束
    }

    @MainThread
    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
        if (mStatus != Status.PENDING) {// 正在執行或執行結束的任務不能重復調用execute
            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中任務的執行過程。
那么,大家是否注意到了,真正提交給線程執行的竟然是FutureTask,為什么呢?根據上文的源碼,不難發現,因為FutureTask同時實現了Runnable和Future接口,既能被線程執行,又能提供線程執行任務后的返回結果,還支持任務的取消。

現在,大家最關心的應該是任務的執行結果又是如何反饋到我們的主線程中的呢?我之前一篇文章中寫過Android的消息機制Handler,顯然就是通過Handler來實現的異步消息處理啦。

InternalHandler

private static class InternalHandler extends Handler {
        public InternalHandler() {
            super(Looper.getMainLooper());// 主線程的Loope對象,關聯了主線程及其消息隊列
        }

        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:// 執行結束(正常執行over或者cancel掉)
                    // There is only one result
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:// 更新進度
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }

我們通過給Handler指定主線程的Looper對象,即關聯上了主線程的消息循環系統,通過該handler發送的任何消息都會被遞交到主線程的消息隊列內。handler消息obj部分使用AsyncTaskResult類進行了封裝,包含了當前的AsyncTask和傳遞的數據內容。
那么這兩種消息都是什么時候發送的呢?細心的同學應該早就注意到了postResult方法,就是調用這個調用Handler向主線程發送的結果數據。

// 構造器中實例化的WorkerRunnable的call方法被調用(即任務被執行,一方面FutureTask的get可以獲取執行結果,另外也通過postResult的方式將消息發送給handler)
private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();// 將消息發送給目標Handler
        return result;
    }

我們注意到當handler獲取到了執行結果后,調用了AsyncTask的finish方法,這個方法中發生什么了呢?

private void finish(Result result) {
        if (isCancelled()) {// 如果任務被取消,回調onCancelled
            onCancelled(result);
        } else {// 任務正常執行結束,回調onPostExecute
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }

其實從源碼分析,如果任務已經執行了,調用cancel并不會真正的把任務結束,而是繼續執行,只是改變的是執行之后的回調方法是 onPostExecute還是onCancelled
最后我們再深入了解一下,任務是如何被取消的呢?

 public final boolean cancel(boolean mayInterruptIfRunning) {// 是否停止當前正在運行的任務執行線程
        mCancelled.set(true);
        return mFuture.cancel(mayInterruptIfRunning);// 通過我們的FutureTask對象的cancel方法,根據參數mayInterruptIfRunning決定內部是否直接interrupt當前線程
    }

因為AsyncTask源碼在Android版本迭代中發生很多變化,該次源碼解剖基于Android 7.1.1。Android 3.0之前有一版本,3.0-4.1之間又有一版本,4.1之后的版本延續至今無大的改動變化。
雖然我們的AsyncTask可以實例化多個對象,但是內部的線程調度sHandler和THREAD_POOL_EXECUTOR(線程池)都屬于類變量,被所有實例所共用。

補充Android-29更新(并不一定是在Android-29開始有的更新,再低的版本尚未探究過線程池這一塊的改動)

1. 線程池超負荷了怎么辦?

設置了拒絕策略,通過一個后備線程池去執行超額的任務,包括執行線程池相關參數都有些許變化如下:

    // 執行線程池核心數調整為固定1,核心線程不設置超時
    private static final int CORE_POOL_SIZE = 1;
    // 執行線程池總量固定為20
    private static final int MAXIMUM_POOL_SIZE = 20;
    // 后備線程池核心即總量設置為5,核心線程也設置超時機制
    private static final int BACKUP_POOL_SIZE = 5;
    // 執行線程池和后備線程池超時時間都設置為3s
    private static final int KEEP_ALIVE_SECONDS = 3;

    sBackupExecutorQueue = new LinkedBlockingQueue<Runnable>();
    sBackupExecutor = new ThreadPoolExecutor(
                            BACKUP_POOL_SIZE, BACKUP_POOL_SIZE, KEEP_ALIVE_SECONDS,
                            TimeUnit.SECONDS, sBackupExecutorQueue, sThreadFactory);
    sBackupExecutor.allowCoreThreadTimeOut(true); // 允許核心線程超時

2. 執行線程池任務緩存上的調整(配合新增的后備線程池)

任務緩存隊列由LinkedBlockingQueue(128)調整為SynchronousQueue,因為執行線程總量固定為20了,所有基本能hold住瞬時大量任務,線程尚未超過總量時,可以直接開啟非核心線程處理,即便線程總量不夠用,還有后備線程池,所以一定量的并發還是能撐住的。另外,沒有太多任務時,只保留一個核心線程,也不需要后備線程池。

static {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                new SynchronousQueue<Runnable>(), sThreadFactory);
        threadPoolExecutor.setRejectedExecutionHandler(sRunOnSerialPolicy);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容