Android中的線程和線程池

Android中的線程

線程,在Android中是非常重要的,主線程處理UI界面,子線程處理耗時操作。如果在主線程中處理耗時操作就會發生ANR,這對一個程序來說是非常致命的,因此耗時操作必須放在子線程中去執行。

在Android系統中,除了Thread外,還有很多AsyncTask、IntentService可以扮演線程角色,另外,HandlerThread也是一種特殊的的線程,盡管它們的表現形式不同于傳統線程Thread,但它們的本質依然是傳統的線程。AsyncTask的底層用到了線程池,IntentService和HandlerThread底層則是直接使用了線程。

AsyncTask封裝了線程池和Handler,它主要是為了方便開發者在子線程中更新UI,實際上還是通過Handler將更新UI的操作從子線程切換到主線程來的。HandlerThread是一種具有消息循環的線程,在它的內部可以使用Handler。IntentService是一個服務,系統對其進行了封裝,使其可以更方便的執行后臺任務, IntentService內部采用HandlerThread來執行任務,當任務執行完后IntentService會自動退出。從任務執行的角度看,IntentService的作用像是一個后臺線程,但IntentService是一種服務,它不容易被系統殺死從而可以盡量保證任務的執行,而如果是一個后臺線程,由于這個時候進程中沒有活動的四大組件,那么這個進程的優先級就會非常低,會很容易被系統殺死,這就是IntentService的優點。

線程的創建和銷毀都需要開銷,在系統中我們不能頻繁的創建線程,如果我們需要大量的線程時,正確的做法是采用線程池,一個線程池中會緩存一定數量的線程,通過線程池就可以避免因為頻繁創建和銷毀線程所帶來的系統開銷。

主線程和子線程

主線程是指進程所擁有的線程,默認一個進程只有一個線程,就是主線程,主線程主要處理界面交互相關的邏輯,因為用戶隨時會和UI界面發生交互,所以主線程必須在任何時候都有較高的響應速度,否則就會產生界面卡頓現象。要保持高響應速度,就要求在主線程中不能執行耗時任務,這時子線程就出場了。除了主線程以外的線程都叫子線程。

Android沿用了Java的線程模型,從Android3.0開始系統要求網絡訪問必須在子線程中進行,否則會訪問失敗,并拋出NetworkOnMainThreadException,這樣做是為了避免主線程由于被耗時操作阻塞從而出現ANR現象。

AsyncTask

AsyncTask是一種輕量級的異步任務類,它可以在線程池中執行后臺任務,然后把執行的進度和最終結果傳遞給主線程并在主線程中更新UI。AsyncTask封裝了ThreadPool和Handler,通過AsyncTask可以更方便地執行后臺任務以及在主線程中訪問UI。

AsyncTask是一個抽象的泛型類,它提供了Params、Progress和Result這三個泛型參數,其中Params表示參數類型,Progress表示后臺任務執行進度的類型,Result表示后臺任務返回的結果類型。如果AsyncTask不需要傳遞具體的參數,那么這三個泛型參數可以用Void代替。

class MainAsyncTask extends AsyncTask<Void, Void, Void> {


    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    @Override
    protected Void doInBackground(Void... params) {
        return null;
    }

    @Override
    protected void onProgressUpdate(Void... values) {
        super.onProgressUpdate(values);
    }

    @Override
    protected void onPostExecute(Void aVoid) {
        super.onPostExecute(aVoid);
    }

    @Override
    protected void onCancelled() {
        super.onCancelled();
    }
}

基本上我們常用的方法就是上面這幾個了。

  • onPreExecute 在主線程中執行,在異步任務執行之前,此方法被調用,一般可以做一些準備工作。

  • doInBackground 在線程池中執行,此方法用于執行異步任務,在此方法中可以通過publishProgress方法來更新任務的執行進度,publishProgress方法會調用onProgressUpdate方法。另外此方法返回任務的執行結果給onPostExecute方法。

  • onProgressUpdate 在主線程中執行,當后臺任務的執行進度發生改變時此方法會被調用。

  • onPostExecute 在主線程中執行,當異步任務執行完后此方法會被調用,它的參數是后臺任務doInBackground的返回值。

  • onCancelled 在主線程中執行,當異步任務被取消時,此方法被調用,此時onPostExecute方法將不會再被調用。

AsyncTask在具體使用過程中,也有一些條件限制:

  • AsyncTask的類必須在主線程中加載,也就是第一次訪問AsyncTask必須是在主線程,在Android4.1及以上版本已被系統自動完成。

  • AsyncTask的對象必須在主線程中創建。

  • execute方法必須在主線程中調用。

  • 不能在程序中直接調用onPreExecute、onPostExecute、doInBackground和onProgressUpdate方法。

  • 一個AsyncTask對象只能執行一次,也就是只能調用一次execute方法,否則會報運行時異常。

  • 在Android1.6之前,AsyncTask是串行執行任務的,Android1.6的時候AsyncTask開始采用線程池來處理并行任務,但是從Android3.0開始,為了避免AsyncTask帶來的并發錯誤,AsyncTask又采用一個線程來串行執行任務。但是在Android3.0及以后的版本中,我們仍然可以通過AsyncTask的executeOnExecutor方法來并行執行任務。

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;
}

看源碼可以發現,executeOnExecutor方法的第一個參數sDefaultExecutor其實是一個串行的線程池,一個進程中所有的AsyncTask全都在這個串行的線程池中排隊執行,executeOnExecutor方法中AsyncTask的onPreExecute方法最先執行,然后線程池開始執行。上面的exec其實就是sDefaultExecutor。

線程池的執行過程:

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
private static InternalHandler sHandler;

private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;

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);
        }
    }
}

從SerialExecutor的實現可以分析AsyncTask的排隊執行過程。首先系統會把AsyncTask的Params參數封裝為FutureTask對象,FutureTask是一個并發類,相當于Runnable,然后把這個FutureTask傳遞給SerialExecutor的execute方法去處理,execute方法則是把FutureTask對象插入到任務隊列mTasks中,上面的offer方法就是把這個對象添加到隊列的最后面。如果這時沒有正在活動的AsyncTask任務,就會調用scheduleNext方法來執行下一個AsyncTask任務。當一個AsyncTask任務執行完后會繼續執行其它任務直到所有的任務都被執行為止,這么看來,AsyncTask默認是串行執行的。

依然是上面的代碼,我們發現AsyncTask內部有兩個線程池:SerialExecutor和THREAD_POOL_EXECUTOR,一個Handler:InternalHandler,其中SerialExecutor用于任務的排隊,THREAD_POOL_EXECUTOR用于真正執行任務,因為在方法scheduleNext中就是使用THREAD_POOL_EXECUTOR.execute方法來執行任務的,InternalHandler則是用于將執行環境從線程池切換到主線程。

AsyncTask的構造方法:

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);
                Binder.flushPendingCommands();
            } catch (Throwable tr) {
                mCancelled.set(true);
                throw tr;
            } finally {
                postResult(result);
            }
            return 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);
            }
        }
    };
}

這個WorkerRunnable的call方法什么時候被調用?還記得線程池剛執行時傳遞了一個參數mFuture嗎?這個mFuture就是AsyncTask構造方法中的這個mFuture,之前我們說過,它是一個FutureTask對象,創建這個FutureTask對象時,我們將這個mWorker傳給了它,FutureTask對象在等待執行的列隊中最后被執行時會調用其run方法,我們再看看FutureTask的run方法:

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();
                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);
    }
}

可以看到在run方法中有調用c.call(),這個c就是FutureTask中的全局變量callable,我們先找這個callable:

public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    this.state = NEW;       // ensure visibility of callable
}

看,在FutureTask的構造方法中,結合我們上面所說,這個callable就是AsyncTask構造方法中的mWorker。

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);
            Binder.flushPendingCommands();
        } catch (Throwable tr) {
            mCancelled.set(true);
            throw tr;
        } finally {
            postResult(result);
        }
        return result;
    }
};

也就是說mWorker的call方法最終也會在線程池中執行。在該方法中,首先將mTaskInvoked設為true,表示當前任務已經被調用過了,然后再執行AsyncTask的doInBackground方法,接著將其返回值傳遞給postResult方法。

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

postResult方法會通過InternalHandler對象發送一個MESSAGE_POST_RESULT消息,InternalHandler類定義如下:

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;
        }
    }
}

由于InternalHandler是一個靜態類,為了能將執行環境切換到主線程,這就要求InternalHandler必須在主線程中創建(原理在‘Android的消息機制’中說過),由于靜態成員會在加載類的時候進行初始化,因此這就變相要求AsyncTask的類必須在主線程中加載。接著上面的,發送MESSAGE_POST_RESULT后,調用了AsyncTask的finish方法:

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

上面這段代碼就很容易理解了,如果AsyncTask被取消了,就調用onCancelled方法,否則調用onPostExecute方法,并設置狀態為結束狀態,而且doInBackground方法的返回值也被傳給了onPostExecute方法。

到這AsyncTask的工作過程就分析完了。

Android3.0及以上版本默認是串行執行的,但是也可以并行執行,采用AsyncTask的executeOnExecutor方法,但是這個方法是從Android3.0新添加的方法,不能在低版本上使用。

HandlerThread

HandlerThread繼承了Thread,它是一種具有消息循環可以使用Handler的Thread,它的實現很簡單,就是在run方法中通過Looper.prepare()來創建消息隊列,并通過Looper.loop()來開啟消息循環,這樣在實際使用中就允許在HandlerThread中創建Handler了。run方法如下:

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

從run方法我們可以發現HandlerThread和普通的Thread有很大區別,普通的Thread主要用于在run方法中執行一個耗時任務,而HandlerThread在內部創建了消息隊列,外界需要通過Handler的消息方式來通知HandlerThread執行一個具體的任務。由于HandlerThread的run方法是一個無限循環,所以當明確不需要再使用HandlerThread時,我們可以通過它的quit或者quitSafely方法來終止線程的執行。

IntentService

IntentService是一種特殊的Service,它繼承了Service并且它是一個抽象類,因此必須創建它的子類才能使用IntentService。IntentService可用于執行后臺耗時的任務,當任務執行完畢后它會自動停止,同時因為它是Service,這導致它的優先級比單純的要高很多,所以它不容易被系統殺死。

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第一次被啟動時,會創建一個HandlerThread,然后使用它的Looper來構造一個Handler對象mServiceHandler,這樣,通過mServiceHandler發送的消息最終都會在HandlerThread中執行,這樣看來,IntentService也適合用于執行后臺任務。

跟Service一樣,每次啟動,IntentService都會調用onStartCommand方法:

/**
 * You should not override this method for your IntentService. Instead,
 * override {@link #onHandleIntent}, which the system calls when the IntentService
 * receives a start request.
 * @see android.app.Service#onStartCommand
 */
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
    onStart(intent, startId);
    return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

我們看官方給我們解釋的內容:不應該重寫onStartCommand這個方法,而是重寫onHandleIntent方法,因為每次Intentservice接收到啟動請求時都會調用onHandleIntent方法。在onStartCommand方法中每次都會調用onSatrt方法

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

也就是每次啟動就發送一條消息(包含了后臺任務的Intent)到HanderThead去處理,而ServiceHandler收到消息后會將Intent傳遞給onHandleIntent方法去處理。

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);
    }
}

這個Intent對象的內容和外界的startService(intent)中的intent的內容是完全一致的,通過這個Intent對象就可以解析出外界啟動IntentService時所傳遞的參數,然后根據參數區分具體的后臺任務。

當onHandleIntent方法執行完后,IntentService就會通過stopSelf(int startId)方法來嘗試停止服務,為什么說是嘗試呢?這就是stopSelf方法和stopSelf(int startId)方法的區別,stopSelf方法會立即停止服務,而這個時候可能還有其他消息未處理完,stopSelf(int startId)方法則會等到所有消息處理完后才停止服務。stopSelf(int startId)在嘗試停止服務之前會判斷最近啟動服務的次數是否和startId相等,相等就立即停止服務,否則就不停止。

另外,由于每執行一個后臺任務就啟動一次IntentService,而IntentService內部則是通過消息的方式向HandlerThread請求執行任務,Handler中的Looper是順序處理消息的,所以IntentService也是順序執行后臺任務的,當有多個后臺任務時,這些任務也會按照外界發起的順序依次執行。執行完最后一個時IntentService停止。

Android中的線程池

使用線程池的好處:

  • 重用線程池中的線程,避免因為線程的創建和銷毀所帶來的性能開銷

  • 能有效控制線程池的最大并發數,避免大量的線程之間因為互相搶占系統資源而導致阻塞現象

  • 能夠對線程進行簡單管理,并提供定時執行以及指定間隔循環執行等功能

ThreadPoolExecutor

ThreadPoolExecutor是線程池的真正實現,它的構造方法提供一系列參數來配置線程池,各個參數的含義:

  • corePoolSize:線程池的核心線程數,默認情況下,核心線程會在線程池中一直存活,即使它們處于閑置狀態,如果將ThreadPoolExecutor的allowCoreThreadTimeOut屬性設置為true,那么閑置的核心線程在等待新任務到來時會有超時策略,這個時間間隔由keepAliveTime指定,當等待時間超過keepAliveTime指定的時長后,核心線程就會被終止。

  • maximumPoolSize:線程池所能容納的最大線程數,當活動線程數達到這個數值后,后續的新任務將會被阻塞。

  • keepAliveTime:非核心線程閑置時的超時時長,超過這個時長,非核心線程就會被回收,當allowCoreThreadTimeOut設置為true時,keepAliveTime同樣會作用于核心線程。

  • unit:用于指定keepAliveTime參數的時間單位,這是一個枚舉,常用的有TimeUnit.MILLISECONDS、TimeUnit.SECONDS、TimeUnit.MINUTES。

  • workQueue:線程池中的任務隊列,通過線程池的execute方法提交的Runnable對象會存儲在這個參數中。

  • threadFactory:線程工廠,為線程池提供創建新線程的功能。ThreadFactory是一個接口,它只有一個方法:newThread(Runnable r)

  • RejectedExecutionHandler handler:這個參數不常用,當線程池無法執行新任務時,可能是由于任務隊列已滿或者是無法成功執行任務,這時,ThreadPoolExecutor會調用handler的rejectedExecution方法來通知調用者,默認情況下,rejectedExecution方法會直接拋出一個RejectedExecutionException,ThreadPoolExecutor為RejectedExecutionHandler提供了幾個可選值,CallerRunsPolicy、AbortPolicy、DiscardPolicy、DiscardOldestPolicy,它們都是RejectedExecutionHandler的實現類,其中AbortPolicy是默認值。

ThreadPoolExecutor執行任務時遵循下列規則:

  • 如果線程池中的線程數量未達到核心線程的數量,那么會直接啟動一個核心線程來執行任務。

  • 如果線程池中的線程數量已達到或超過核心線程的數量,那么任務會被插入到任務隊列中排隊等待執行。

  • 如果在上面這條中無法將任務插入到任務隊列中,一般是因為任務隊列已滿,這時如果線程數量未達到線程池規定的最大值,那么會會立刻啟動一個非核心線程來執行任務。

  • 如果上面這條種線程數量已經達到線程池規定的最大值,那么久拒絕執行此任務,ThreadPoolExecutor就會調用RejectedExecutionHandler的rejectedExecution方法來通知調用者。

下面這是AsyncTask的線程池配置:

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;
}

規則基本是這樣,以后我們自定義線程池也可以參照這樣:

核心線程數 = CPU核心數 + 1
線程池最大線程數 = CPU核心數 * 2 + 1
核心線程無超時機制,非核心線程在閑置時的超時時間 = 1s
任務隊列容量 = 128

線程池分類

Android中常見的四種線程池:

  • FixedThreadPool:是一種線程數量固定的線程池,當線程處于空閑狀態時不會被回收,除非線程池被關閉。當所有的線程都處于活動狀態時新任務會處于等待狀態,直到有線程空閑出來。由于FixedThreadPool只有核心線程,并且這些核心線程不會被回收,所以它能夠更快速的響應外界的請求。它的任務隊列也沒有大小限制。

  • CachedThreadPool:是一種線程數量不定的線程池,它只有非核心線程,并且最大線程數為Integer.MAX_VALUE,這種線程池中的空閑線程都有超時機制,時長為60s。和FixedThreadPool不同的是,CachedThreadPool的任務隊列其實相當于一個空集合,無法存儲任務,所以任何任務都會立即被執行。這個特性決定了CachedThreadPool適合執行大量的耗時較少的任務,當整個線程池都處于閑置狀態時,線程池中的線程都會超時而被停止,此時,CachedThreadPool中實際是沒有任何線程的,幾乎不占任何系統資源。

  • ScheduledThreadPool:它的核心線程數是固定的,非核心線程數沒有限制,并且當非核心線程閑置時會被立刻回收。這類線程池主要用于執行定時任務和具有固定周期的重復任務。

  • SingleThreadPool:它只有一個核心線程,它確保所有的任務都在同一個線程中按順序執行,SingleThreadPool的意義在于統一所有的外界任務到一個線程中,使得在這些任務之間不需要處理線程同步的問題。

這四種線程池的創建方式:

四種線程池的創建方式

除了系統這四種線程池,還可以根據實際需要靈活地配置線程池。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 從用途上來說,線程分為主線程和子線程,主線程主要處理和界面相關的事情,子線程則往往用于執行耗時操作。 除了Thre...
    小柏不是大白閱讀 645評論 0 3
  • Android中扮演線程角色,除了 Thread 之外,AsyncTask 和 IntentService 也是。...
    L_Xian閱讀 190評論 0 0
  • 今天原本是新家進家具的日子結果因為家具廠卡車超載了被警察抓住了。不是我說鄉下的裝修,他們總是一拖再拖,明明人手不夠...
    清茶苗苗閱讀 180評論 0 0
  • 明知沒意義,卻無法不執著的事物——誰都有這樣的存在。其實,偶爾也會在談話的間隙中多偷看你兩眼,真想讓你知道,那一刻...
    溫不溫暖都是心閱讀 253評論 0 0
  • 青春是什么樣子的?我不知道。但大多數的回憶青春,在我看來,都是一種逃離,逃離的路上,我們顛沛流離。 最近三年,隨著...
    人亦北上閱讀 430評論 0 2