首頁 > 軟體

一文徹底瞭解Android中的執行緒和執行緒池

2022-12-21 14:00:10

前言

從用途上來說Android的執行緒主要分為主執行緒和子執行緒兩類,主執行緒主要處理和介面相關的工作,子執行緒主要處理耗時操作。除Thread之外,Android中還有其他扮演執行緒的角色如AsyncTask、IntentService、HandleThread,其中AsyncTask的底層用到了執行緒池,IntentService和HandleThread的底層直接使用了執行緒。

AsyncTask內部封裝了執行緒池和Handler主要是為了方便開發者在線上程中更新UI;HandlerThread是一個具有訊息迴圈的執行緒,它的內部可以使用Handler;IntentService是一個服務,系統對其進行了封裝使其可以更方便的執行後臺任務,IntentService內部採用HandleThread來執行任務,當任務執行完畢後IntentService會自動退出。IntentService是一個服務但是它不容易被系統殺死因此它可以儘量的保證任務的執行。

1.主執行緒和子執行緒

主執行緒是指程序所擁有的的執行緒,在Java中預設情況下一個程序只能有一個執行緒,這個執行緒就是主執行緒。主執行緒主要處理介面互動的相關邏輯,因為介面隨時都有可能更新因此在主執行緒不能做耗時操作,否則介面就會出現卡頓的現象。主執行緒之外的執行緒都是子執行緒,也叫做工作執行緒。

Android沿用了Java的執行緒模型,也有主執行緒和子執行緒之分,主執行緒主要工作是執行四大元件及處理他們和使用者的互動,子執行緒的主要工作就是處理耗時任務,例如網路請求,I/O操作等。Android3.0開始系統要求網路存取必須在子執行緒中進行否則就會報錯,NetWorkOnMainThreadException

2.Android中的執行緒形態

2.1 AsyncTask

AsyncTask是一個輕量級的非同步任務類,它可以線上程池中執行非同步任務然後把執行進度和執行結果傳遞給主執行緒並在主執行緒更新UI。從實現上來說AsyncTask封裝了Thread和Handler,通過AsyncTask可以很方便的執行後臺任務以及主執行緒中存取UI,但是AsyncTask不適合處理耗時任務,耗時任務還是要交給執行緒池執行。

AsyncTask的四個核心類如下:

    • onPreExecute():主要用於做一些準備工作,在主執行緒中執行非同步任務執行之前
    • doInBackground(Params ... params):線上程池執行,此方法用於執行非同步任務,params表示輸入的引數,在此方法中可以通過publishProgress方法來更新任務進度,publishProgress會呼叫onProgressUpdate
    • onProgressUpdate(Progress .. value):在主執行緒執行,當任務執行進度發生改變時會呼叫這個方法
    • onPostExecute(Result result):在主執行緒執行,非同步任務之後執行這個方法,result引數是返回值,即doInBackground的返回值。

2.2 AsyncTask的工作原理

2.3 HandleThread

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

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

HandleThread和Thread的區別是什麼?

    • Thread的run方法中主要是用來執行一個耗時任務;
    • HandleThread在內部建立了一個訊息佇列需要通過Handler的訊息方式來通知HandleThread執行一個具體的任務,HandlerThread的run方法是一個無限迴圈因此在不使用是呼叫quit或者quitSafely方法終止執行緒的執行。HandleTread的具體使用場景是IntentService。

2.4 IntentService

IntentService繼承自Service並且是一個抽象的類因此使用它時就必須建立它的子類,IntentService可用於執行後臺耗時的任務,當任務執行完畢後就會自動停止。IntentService是一個服務因此它的優先順序要比執行緒高並且不容易被系統殺死,因此可以利用這個特點執行一些高優先順序的後臺任務,它的實現主要是HandlerThread和Handler,這點可以從onCreate方法中瞭解。

//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方法,在onCreate方法中會建立HandlerThread,然後使用它的Looper建立一個Handler物件ServiceHandler,這樣通過mServiceHandler把訊息傳送到HandlerThread中執行。每次啟動IntentService都會呼叫onStartCommand,IntentService在onStartCommand中會處理每個後臺任務的Intent。

//IntentService#onStartCommand
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
    onStart(intent, startId);
    return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

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

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

onStartCommand是如何處理外界的Intent的?

在onStartCommand方法中進入了onStart方法,在這個方法中IntentService通過mserviceHandler傳送了一條訊息,然後這個訊息會在HandlerThread中被處理。mServiceHandler接收到訊息後會把intent傳遞給onHandlerIntent(),這個intent跟啟動IntentService時的startService中的intent是一樣的,因此可以通過這個intent解析出啟動IntentService傳遞的引數是什麼然後通過這些引數就可以區分具體的後臺任務,這樣onHandleIntent就可以對不同的後臺任務做處理了。當onHandleIntent方法執行結束後IntentService就會通過stopSelf(int startId)方法來嘗試停止服務,這裡不用stopSelf()的原因是因為這個方法被呼叫之後會立即停止服務但是這個時候可能還有其他訊息未處理完畢,而採用stopSelf(int startId)方法則會等待所有訊息都處理完畢後才會終止服務。呼叫stopSelf(int startId)終止服務時會根據startId判斷最近啟動的服務的startId是否相等,相等則立即終止服務否則不終止服務。

每執行一個後臺任務就會啟動一次intentService,而IntentService內部則通過訊息的方式向HandlerThread請求執行任務,Handler中的Looper是順序處理訊息的,這就意味著IntentService也是順序執行後臺任務的,當有多個後臺任務同時存在時這些後臺任務會按照外界發起的順序排隊執行。

3.Android中的執行緒池

執行緒池的優點:

    • 執行緒池中的執行緒可重複使用,避免因為執行緒的建立和銷燬帶來的效能開銷;
    • 能有效控制執行緒池中的最大並行數避免大量的執行緒之間因互相搶佔系統資源導致的阻塞現象;
    • 能夠對執行緒進行簡單的管理並提供定時執行以及指定間隔迴圈執行等功能。

Android的執行緒池的概念來自於Java中的Executor,Executor是一個介面,真正的執行緒的實現是ThreadPoolExecutor,它提供了一些列引數來設定執行緒池,通過不同的引數可以建立不同的執行緒池。

3.1 ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

ThreadPoolExecutor是執行緒池的真正實現,它的建構函式中提供了一系列引數,先看一下每個引數的含義:

    • corePoolSize:執行緒池的核心執行緒數,預設情況下核心執行緒會線上程池中一直存活即使他們處於閒置狀態。如果將ThreadPoolExecutor的allowCoreThreadTimeOut置為true那麼閒置的核心執行緒在等待新的任務到來時會有超時策略,超時時間由keepAliveTime指定,當等待時間超過keepAliveTime設定的時間後核心執行緒就會被終止。
    • maxinumPoolSize:執行緒池中所能容納的最大執行緒數,當活動執行緒達到做大數量時後續的新任務就會被阻塞。
    • keepAliveTime:非核心執行緒閒置時的超時時長,超過這個時長非核心執行緒就會被回收。
    • unit:用於指定超時時間的單位,常用單位有毫秒、秒、分鐘等。
    • workQueue:執行緒池中的任務佇列,通過執行緒池中的execute方法提交的Runnable物件會儲存在這個引數中。
    • threadFactory:執行緒工廠,為執行緒池提供建立新的執行緒的功能。
    • handler:這個引數不常用,當執行緒池無法執行新的任務時,這可能是由於任務佇列已滿或者無法成功執行任務,這個時候ThreadPoolExecutor會呼叫handler的rejectExecution方法來通知呼叫者。

ThreadPoolExecutor執行任務時大致遵循如下規則:

  • 如果執行緒池中的執行緒數量沒有達到核心執行緒的數量那麼會直接啟動一個核心執行緒來執行任務;

    如果執行緒池中執行緒數量已經達到或者超過核心執行緒的數量那麼會把後續的任務插入到佇列中等待執行;

    如果任務佇列也無法插入那麼在基本可以確定是佇列已滿這時如果執行緒池中的執行緒數量沒有達到最大值就會立刻建立非核心執行緒來執行任務;

    如果非核心執行緒的建立已經達到或者超過執行緒池的最大數量那麼就拒絕執行此任務,同時ThreadPoolExecutor會通過RejectedExecutionHandler丟擲異常rejectedExecution。

3.2執行緒池的分類

  • FixedThreadPool:它是一種數量固定的執行緒池,當執行緒處於空閒狀態時也不會被回收,除非執行緒池被關閉。當所有的執行緒都處於活動狀態時,新任務都會處於等待狀態,直到有空閒執行緒出來。FixedThreadPool只有核心執行緒並且不會被回收因此它可以更加快速的響應外界的請求。
  • CacheThreadPool:它是一種執行緒數量不定的執行緒池且只有非核心執行緒,執行緒的最大數量是Integer.MAX_VALUE,當執行緒池中的執行緒都處於活動狀態時如果有新的任務進來就會建立一個新的執行緒去執行任務,同時它還有超時機制,當一個執行緒閒置超過60秒時就會被回收。
  • ScheduleThreadPool:它是一種擁有固定數量的核心執行緒和不固定數量的非核心執行緒的執行緒池,當非核心執行緒閒置時會立即被回收。
  • SignleThreadExecutor:它是一種只有一個核心執行緒的執行緒池,所有任務都在同一個執行緒中按順序執行。

總結

到此這篇關於Android中執行緒和執行緒池的文章就介紹到這了,更多相關Android執行緒和執行緒池內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com