1. 程式人生 > >Android中的執行緒與執行緒池

Android中的執行緒與執行緒池

執行緒與執行緒池

概括

執行緒分為主執行緒和子執行緒. 主執行緒主要是用來處理和介面相關的事情, 子執行緒主要是用來做耗時的操作,比如 載入遠端資料,資料庫操作等。

在android 中,處理直接使用 Thread以外。 android 還提供了很多類似執行緒的操作便於我們開發, 有 AsyncTask 和 IntentService ,還有 HandlerThread. 他們都有各自的特性。 AsyncTask 底層使用的是 handler + 執行緒池。 而 IntentService 和 HandlerThread 內部則 直接使用的執行緒。

IntentService 的優點是 它是以Service 元件的形式存在,當在後臺執行任務時,不容易被系統殺死。

AsyncTask

AsyncTask 封裝了 Handler 和 執行緒池。 所以能夠更加方便的在後臺執行任務,輕鬆的切換到主執行緒更新UI。

AsyncTask 是一個抽象類,提供 Params 、Progress 和 Result 三個泛型引數。

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

如果不需要傳遞引數,可以使用 Void 來代替。

AsyncTask 有四個核心方法:

  • onPreExecute() : 執行在Ui 執行緒,執行在 doInBackground 之前,一般做些準備工作。
/**
     * Runs on the UI thread before {@link #doInBackground}.
     *
     * @see #onPostExecute
     * @see #doInBackground
     */
    protected void onPreExecute() {
    }
  • doInBackground(Params… params) 這個方法是在子執行緒中執行。可以用來執行計算、訪問網路等耗時操作, params 表示 執行任務所需要的引數。 在此方法中可以通過 publishProgress 來更新任務的進度。 publishProgress會引起 onProgressUpdate方法的回撥。 doInBackground 需要返回結果給 onPostExecute。

  • onProgressUpdate(Progress… values) 執行在ui 執行緒。當任務執行進度發生改變時,會被呼叫。

  • onPostExecute(Result result) 執行在ui執行緒, 當非同步的任務執行完畢,會把結果傳遞給它。

上述幾個方法的執行順序是: onPreExecute() -> doInBackground -> onPostExecute.

AsyncTask 還提供了 onCancelled()方法,執行在主執行緒。 當任務被取消時,該方法會被回撥。這個時候 onPostExecute 則不會被呼叫。

··· 為可變引數

AsyncTask 必須在主執行緒中建立,其 execute 也必須在主執行緒中執行。 Android 4.1後的版本,系統已經自動完成。在Android5.0的原始碼中 ActivityThread 的 main 方法中,會呼叫 init方法。

AsyncTask.init();

不要在程式中直接呼叫 onPreExecute…等的方法,一個AsyncTask物件只能執行一次,即只能呼叫一次 execute方法,否則會丟擲異常。

在1.6之前是 AsyncTask 是序列執行任務的, 從1.6開始 採用的是並行的,但從3.0開始,為了避免AsyncTask 所帶來的併發錯誤, Async 又採用了 序列來執行任務。 但我們任然可以通過 executeOnExecutor 來並行執行任務

AsyncTask 原始碼分析

在 execute 方法中, 呼叫了 executeOnExecutor:

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

我們來看 executeOnExecutor的程式碼:

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

         ...// 省略程式碼

        mStatus = Status.RUNNING;

        onPreExecute();

        mWorker.mParams = params;
        exec.execute(mFuture);

        return this;
    }

sDefaultExecutor是一個序列的執行緒池。 所有的AsyncTask 都在這裡排隊執行。 從上面的程式碼看出,首先是 onPreExecute 先執行。

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

上面看到 是呼叫了 exec.execute 即 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);
            }
        }
    }

從上面的程式碼可以分析出 AsyncTask 是排隊執行的。 在AsyncTask的建構函式中,系統會把 Params 封裝成 FutureTask 物件。 FutureTask 是一個併發類,在這裡充當了 Runnable。 SerialExecutor 的execute 方法首先會把 FutureTask物件插入到 任務佇列 mTask 中,然後執行 scheduleNext()方法。當任務執行完畢後,會呼叫 finally 部分,繼續執行其他的任務,直到所有的任務都執行完畢。從這點可以看出 AsyncTask是 序列執行的。

AsyncTask 內部有兩個執行緒池 SERIAL_EXECUTORTHREAD_POOL_EXECUTOR, 還有一個Handler(InternalHandler)。 SERIAL_EXECUTOR 是用來進行 任務的排隊。 而 THREAD_POOL_EXECUTOR 是真正在執行任務的執行緒池。 InternalHandler 負責進行執行緒的切換。

在上面的程式碼中,會呼叫 FutureTask的run 方法。而 FutureTask分裝了 mWorker(內部有Params的引用),會呼叫mWorker 的 call 方法。所以mWorker的 Call方法會線上程池中執行。 下面是程式碼:

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

                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                return postResult(doInBackground(mParams));
            }
        };

mTaskInvoked 設為true,表示 當前任務已經被呼叫過了。 然後執行 AsyncTask的 doInBackground.然後把其返回值傳遞給 postResult。

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

會發送一個訊息到 InternalHandler. 該訊息的處理為一下程式碼。

result.mTask.finish(result.mData[0]);

result.mTask即 AsyncTask。 所以我們來看 finish 方法。

 private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }

這裡呢,如果任務被取消了就呼叫 onCancelled,否則就會呼叫 onPostExecute方法。 而doInBackground 的結果會傳遞到 onPostExecute。

到這裡的分析就結束了。

HandlerThread

HandlerThread 繼承了 Thread, 它是一種可以使用Handler的 Thread 實現,在run 方法中 通過 Looper.prepare()的方式建立訊息佇列, 通過Looper.loop()開啟訊息迴圈。 這樣的話,就可以在 HandlerThread中建立 Handler了。

@Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

上面是其run 方法的實現。

區別於普通的Thread: 普通的Thread是使用run方法來執行耗時操作, HandlerThread的內部卻是建立了訊息的佇列。 外界需要通過 Handler的訊息方式來通知 HandlerThread 執行一個具體的任務。

當我們不需要使用的時候,可以通過 quit 或者 quitSafely方法來終止執行緒的執行

具體的使用場景: IntentService.

IntentService

IntentService是一種特殊的Service。 繼承自Service 並且是一個抽象類。 區別於普通的Service,IntentService可以用於執行後臺耗時的操作。 而且執行完畢後,會自動停止。

IntentService 與 普通的執行緒 : 相比之下,IntentServie 是一個服務元件,優先順序比較高,不容易被系統殺死。

IntentService的內部 封裝了 HandlerThread 和 Handler 。 我們來看 IntentService的 onCreate方法:

@Override
    public void onCreate() {
        // TODO: It would be nice to have an option to hold a partial wakelock
        // during processing, and to have a static startService(Context, Intent)
        // method that would launch the service & hand off a wakelock.

        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

當IntentService 被啟動,其onCreate方法會被呼叫, 會建立 HandlerThread, 並獲取它的 Looper來構建 Handler物件,即 mServiceHandler。 mServiceHandler 所傳送的訊息 最終都會在 HandlerThread 中執行。

每次啟動 IntentService 它的 onStartCommand 都會被呼叫一次。 在onStartCommand 中可以獲取到 任務的 Intent 引數。

在 onStartCommand 中又呼叫了 onStart.

@Override
    public void onStart(Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

從上面可以看到 在onStart 中 傳送了一個訊息到 mServiceHandler。 在Handler 內部 又會將 Intent引數傳遞給 onHandleIntent 去處理。 onHandleIntent是發生在 HandlerThread 執行緒。

這個Intent物件的內容和外界的startService(intent) 中的 intent的內容是完全一致的, 通過這Intent 可以解析到外界所 傳遞的引數。通過這個引數可以區分具體的後臺任務

當任務結束後,會呼叫 stopSelf(int startId) 來停止服務。

注意 stopSelf() 和 stopSelf(int startId)

stopSelf()會立即停止服務 , 而stopSelf(int startId) 在嘗試停止之前會判斷最近啟動服務的次數是否和 startId相等。如果相等則會立即停止服務,不相等則不停止(可以從 AMS的 stopServiceToken 的實現中找到依據)。

ServiceHandler的程式碼:

private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

從上面的整體流程中,可以知道 IntentService 是序列的。 按照外界發起的順序排隊執行。

Android中的執行緒池

執行緒池的好處:

1.重用執行緒池中的執行緒,避免因為執行緒的建立和銷燬所帶來的效能開銷。
2.能有效控制執行緒池的最大併發數,避免大量的執行緒之間因互相搶佔系統志願而導致的阻塞現象。
3.能夠對執行緒進行簡單的管理,並指定定時執行 以及 指定間隔迴圈執行等功能。

Android中執行緒池的概念來自於 Java中的 Executor, Executor是一個介面,真正的執行緒實現為ThreadPoolExecutor.

ThreadPoolExecutor 提供了很多引數可以來配置執行緒池。 Android還提供了4類執行緒池(可以通過提供的工廠方法獲得),內部都是通過配置ThreadPoolExecutor來實現的。

ThreadPoolExecutor 是執行緒池的真正實現,下面是一個比較常用的構造方法:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

corePoolSize: 執行緒池的核心執行緒數,預設的情況下,核心執行緒會線上程池中一直存活,即使是處於空閒的狀態, 如果將 ThreadPoolExecutor 的 allowCoreThreadTimeOut 設定為 true, 那麼閒置的核心執行緒在等待任務時會有超時策略,這個是由 keepAliveTime 來指定。 當超過時,核心執行緒就會被終止。

maximumPoolSize: 執行緒池所能容納的最大執行緒數,當執行緒池到達這個數值後,後續的新任務將會被阻塞。

keepAliveTime: 非核心執行緒閒置時的超時時長, 超過這個時長, 非核心執行緒就會被回收。當 allowCoreThreadTimeOut 設定為 true 時, keepAliveTime 同樣會作用於核心執行緒。

unit 用於指定keepAliveTime 引數的時間單位, TimeUnit 是一個列舉, 常用的有 MILLISECONDS、SECONDS、MINUTES 等等。

workQueue: 執行緒池中的任務佇列, 通過 執行緒池的 execute方法所提交的Runnable 物件都會儲存在這個引數中。

threadFactory: 執行緒工廠,為執行緒池提供建立新執行緒的功能。 ThreadFactory 是一個介面,它只有一個方法 Thread newThread(Runnable r);

除了上面的,還有個不常用的引數 RejectedExecutionHandler,當執行緒池無法執行任務時,會通過呼叫handler的 rejectedExecution 方法來通知呼叫者。 預設情況下是 丟擲一個 RejectedExecutionException 的異常。 還有一些 其他的可選值 策略 AbortPolicy 、DiscardPolicy 等. 預設是 AbortPolicy,即丟擲一個異常。

執行緒池的配置方式可以參考 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秒;
  • 任務佇列的容量為128.

執行緒池的分類

Android中常見的四類執行緒 可以通過 Executors 的工廠方法獲取到 分別是 FixedThreadPool 、CachedThreadPool、ScheduledThreadPool 、SingleThreadExecutor. 它們的內部都是通過直接或者間接的配置 ThreadPoolExecutor 來實現的。

FixedThreadPool

它是一種執行緒數量固定的執行緒池,當執行緒處於空閒狀態時,它們並不會被回收,除非執行緒池被關閉了,當所有的執行緒都處於活動狀態時,新任務都會處於等待狀態,直到有執行緒空閒出來。由於 FixedThreadPool 只有核心執行緒 並且這些核心執行緒不會被回收,所以能夠更快速的響應外界的請求。 這寫 核心執行緒是沒有超時機制的, 任務佇列也沒有大小的限制。

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

CachedThreadPool

通過 Executors newCachedThreadPool 方法來建立,它是一種數量不定的執行緒池,而且 只有非核心執行緒, 並且最大的執行緒數是 Integer.MAX_VALUE.是一個很大的數,相當於是 任意大。 當執行緒池的執行緒都處於工作狀態是, 執行緒池會建立新的執行緒來處理新任務。 執行緒池中空閒執行緒是有超時機制的,時長為 60s。超過 60s的閒置執行緒,就會被回收。CachedThreadPool的任務佇列相當於一個空的集合, 這會導致任何任務都會被立即執行。SynchronousQueue 很多情況下 可以把它簡單理解為一個無法儲存元素的佇列。

當執行緒池中的執行緒都處理閒置狀態時, 執行緒都會超時而被停止。這個時候執行緒池實際上是沒有任何執行緒的,幾乎不佔用系統的資源。

 public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

ScheduledThreadPool

通過 ExecutorsnewScheduledThreadPool 方法來建立,它的核心執行緒數是固定的。而非核心執行緒數是沒有限制的。 非核心執行緒的超時策略中 keepAliveTime 0 . 即代表只要 執行緒一閒置,就會被立即回收掉。 這中執行緒池主要用來執行定時任務和具有固定週期的重複任務。

 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }


public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

SingleThreadExecutor

通過 ExecutorsnewSingleThreadExecutor 方法來建立。這個執行緒池內部只有一個核心執行緒。並且最大執行緒數也是1 。它確保所有的任務都在同一個執行緒中按順序執行。 SingleThreadExecutor 在於統一外界所有的任務到一個執行緒中。使得這些任務不需要處理同步的問題。

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

到這執行緒池的相關內容就講完了。