九、Android 的執行緒和執行緒池
0. 幾個概念
在作業系統中,執行緒 是作業系統排程的最小單元,同時執行緒也是一種受限的系統資源,即執行緒不可能無限制的產生,並且執行緒的建立和銷燬都會有相應的開銷。
在 Android 中,從用途 上來說,執行緒分為主執行緒(又叫 UI 執行緒) 和子執行緒(又叫工作執行緒) ,其中主執行緒 主要處理和介面相關的事情,而子執行緒 則往往用於執行耗時操作。
1. HandlerThread
Handler的原理前面已經講過,往往是在一個執行緒中執行Looper,其他執行緒通過Handler來發送訊息到Looper所線上程,這裡涉及執行緒間的通訊。既然涉及多個執行緒的通訊,會有同步的問題,Android對此直接提供了HandlerThread類,下面來講講HandlerThread類的設計。
HandlerThread 繼承了 Thread,它是一種可以使用 Handler 的 Thread,它的實現也很簡單。
@Override public void run() { mTid = Process.myTid();//獲取執行緒的 tid Looper.prepare();//建立 Looper 物件 synchronized (this) { mLooper = Looper.myLooper();//獲取 Looper 物件 notifyAll();//喚醒等待執行緒 } Process.setThreadPriority(mPriority); onLooperPrepared();//該方法為空實現,可自己重寫實現自己的邏輯 Looper.loop();//進入迴圈模式 mTid = -1; }
可以看到在 run 方法裡開啟了 Looper 迴圈。而且它有個 getThreadHandler 方法,用於獲取這個執行緒的 Handler。如下所示。
/** * @return a shared {@link Handler} associated with this thread * @hide */ @NonNull public Handler getThreadHandler() { if (mHandler == null) { mHandler = new Handler(getLooper()); } return mHandler; }
用法:
// Step 1: 建立並啟動HandlerThread執行緒,內部包含Looper HandlerThread handlerThread = new HandlerThread("gityuan.com"); handlerThread.start(); // Step 2: 得到Handler Handler handler = handlerThread.getThreadHandler(); // Step 3: 傳送訊息 handler.post(new Runnable() { @Override public void run() { System.out.println("thread id="+Thread.currentThread().getId()); } });
當然如果你明確不需要再使用時,可以通過它的 quit 或者 quitSafely 來終止執行緒的執行。
它在 Android 中的一個具體的使用場景就是 IntentService。
2. IntentService
IntentService 原始碼也不復雜, 當你瞭解了 HandlerThread 後,會異常簡單。
首先 IntentService 是一個抽象類,所以我們需要建立它的子類並實現它的抽象方法 onHandleIntent(Intent intent) 。
IntentService 可用於執行耗時的後臺任務,當任務執行完後它會自動停止,同時由於它是服務的原因,所以它的優先順序比單純的執行緒要高很多。
注意:Service 是執行在主執行緒的,它裡面不能做耗時任務。
Android的後臺是指,它的執行是完全不依賴UI的。
所以 IntentService 的出現方便的解決了這些問題,下面我們一一解答上面所說的那些功能,比如能執行耗時的後臺任務,任務執行後會自動停止等等。
首先看 IntentService 的 onCreate 方法。
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); }
這裡面很簡答,我們剛剛瞭解了 HandlerThread 的概念,所以這裡就是建立一個 HandlerThread,然後建立一個 ServiceHandler,這個 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 的內部類,一個 Handler 而已, onHandleIntent 是我們要實現的抽象方法,stopSelf則很好的說明了當任務執行後它會自動停止 這點。
那麼能執行耗時的後臺任務 這一點相比大家也明白了,但是還是要說清楚一點。
- 首先,我們知道 Service 是執行在主執行緒的,當 onCreate 回撥完,我們此時擁有了 handler 的例項即mServiceHandler, 另外在 oncreate 裡還建立了一個 HandlerThread,大家要注意的是 HandlerThread 就是那個執行耗時任務的執行緒, 它不是主執行緒,不要弄混了,不要覺得有 handler 的地方就是在主執行緒裡,大家要記得Handler 的作用是將一個任務切換到 Handler 所在的執行緒中執行 。
清楚了這一點後,想必就沒什麼難點了,onStartCommand 會去呼叫 onStart,然後裡面會將傳進來的 intent 組建成一個 Message 通過 mServiceHandler 傳送訊息,然後在 onHandleIntent 中去處理,注意 onHandleIntent 裡面所執行的都是在另一個執行緒,即 mServiceHandler 所在的執行緒。
@Override public void onStart(@Nullable Intent intent, int startId) { Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; msg.obj = intent; mServiceHandler.sendMessage(msg); } /** * 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; }
3. AsyncTask
關於它的一些定義啥的,這裡就不闡述了。首先看這個類的宣告
public abstract class AsyncTask<Params, Progress, Result>
根據它的宣告,我們可以得到這些資訊,AsyncTask 是一個抽象的泛型類,它提供了 Params, Progress, Result 這三個泛型引數。還有個 抽象方法protected abstract Result doInBackground(Params... params);
- Params : 引數的型別,即 doInBackground(Params... params),就是這個方法的引數型別。
- Progress :後臺任務的執行進度的型別。
- Result :後臺任務的返回結果的型別。
AsyncTask 提供了4個核心方法
- onPreExecute()
- doInBackground(Params... params)線上程池中執行,此方法用於執行非同步任務。
- onProgressUpdate(Progress... values)
- onPostExecute(Result result)
這四個方法的含義也不解釋了,後面看原始碼就會清楚的,當然你應該也瞭解這四個方法的含義。額,我這篇文章只是用來記錄自己的隨想,所以諒解、諒解。
AsyncTask 在具體的使用過程中是有一些限制條件的,主要有如下幾點:
- 1、AsyncTask 的類必須在主執行緒中載入
- 2、AsyncTask 的物件必須在主執行緒中建立
- 3、execute 方法必須在主執行緒中呼叫
- 4、不要在程式中直接呼叫 onPreExecute()、onPostExecute、doInBackground 和 onProgressUpdate 方法
- 5、一個 AsyncTask 物件只能執行一次,即只能呼叫一次 execute 方法,否則會報執行時異常
現在我們針對上述幾個限制條件來分析,這樣帶著問題去分析會讓自己不迷失。
3.1 AsyncTask 的類必須在主執行緒中載入
針對這個問題,Android4.1 之前 AsyncTask類 必須在主執行緒中載入,但是在之後的版本中就被系統自動完成。而在Android5.0 的版本中會在 ActivityThread 的 main方法 中執行 AsyncTask 的 init 方法,而在 Android6.0 中又將init 方法刪除。所以在使用這個 AsyncTask 的時候若是適配更多的系統的版本的話,使用的時候就要注意了。
AsyncTask 內部是通過 Handler 來進行執行緒切換的。所以我們要想在主執行緒中去處理結果,那 Handler 肯定得在主執行緒中去建立。
首先 AsyncTask 在成員變數位置 聲明瞭靜態的 Handler
private static InternalHandler sHandler;
然後在建構函式中可以看到這裡對 mHandler 賦值,其中會呼叫 getMainHandler。
我們一般呼叫的都是那個 無參的建構函式,這裡會傳null給有參的那個, 其中會判斷,如果callbackLooper == null
就會去呼叫 getMainHandler()
public AsyncTask() { this((Looper) null); } public AsyncTask(@Nullable Looper callbackLooper) { mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper() ? getMainHandler() : new Handler(callbackLooper); //省略後續程式碼 }
可以看到,這樣既保證了 Handler 採用主執行緒的 Looper 構建,又使得 AsyncTask 在需要時才被載入。
private static Handler getMainHandler() { synchronized (AsyncTask.class) { if (sHandler == null) { sHandler = new InternalHandler(Looper.getMainLooper()); } return sHandler; } }
3.3 execute 方法必須在主執行緒中呼叫
那為什麼 execute 方法必須在主執行緒中呼叫呢, 我們前面知道 AsyncTask 的 4 個核心方法除了 doInBackground,其餘的都允許在 UI 執行緒,那麼 onPreExecute 這個方法肯定也得執行在主執行緒中。
我們往往呼叫 AsyncTask.execute 方法去執行任務。
public final AsyncTask<Params, Progress, Result> execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); } 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 內部會呼叫 onPreExecute(),這也就解釋了為什麼execute 方法必須在主執行緒中呼叫 ,只有在主執行緒中呼叫, onPreExecute() 方法才會執行在 主執行緒中。
3.4 一個 AsyncTask 物件只能執行一次,即只能呼叫一次 execute 方法,否則會報執行時異常
這個問題就很好解答了,在 3.3 裡面的 executeOnExecutor 方法內部很明確, 如果mStatus != Status.PENDING
,就會丟擲異常。而我們執行一個任務的時候會把mStatus = Status.RUNNING
,因此這個疑惑也解決了。
當然 AsyncTask 還有很多可以講的,比如它內部的 執行緒池,預設是使用 SerialExecutor 序列執行的,還有 InternalHandler 的 handleMessage , 以及它內部的 FutureTask 和 WorkerRunnable 等。 這些只要看原始碼就知道了。 整個 AsyncTask 並不是特別難, 所以它是一種很好用的輕量級的非同步任務類。
4. 執行緒池
執行緒池三個優點:
- 重用執行緒池中的執行緒,避免因為執行緒的建立和銷燬所帶來的效能開銷
- 能有效控制執行緒池的最大併發數,避免大量的執行緒之間因互相搶佔系統資源而導致的阻塞現象
- 能夠對執行緒進行簡單的管理,並提供定時執行以及指定間隔迴圈執行等功能
Java 通過Executors 提供四種執行緒池,分別為:
- newFixedThreadPool :會建立一種執行緒數量固定的執行緒池,它只有核心執行緒,並且這些核心執行緒沒有超時機制,另外任務佇列的大小也是沒有限制的。
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
- newCachedThreadPool :會建立一中執行緒數量不定的執行緒池,它只有非核心執行緒, 並且最大執行緒數為 Integer.MAX_VALUE , 該執行緒池中的執行緒都有超時機制 60秒, 另外 SynchronousQueue 是一個非常特殊的佇列,它可以簡單理解為一個無法儲存元素的佇列,這就導致任何任務都會立即執行。這類執行緒池適合執行大量的耗時較少的任務。
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
- newScheduledThreadPool :主要用於執行 定時任務 和 具有固定週期的重複任務
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); } public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS, new DelayedWorkQueue()); }
- newSingleThreadExecutor :這類執行緒池內部只有一個核心執行緒,它確保所有的任務都在同一個執行緒中按順序去執行。
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
可以看到這幾種執行緒池的本質就是 通過不同的引數初始化一個 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 引數的時間單位。
- workQueue : 執行緒池中的任務佇列,通過執行緒池的 execute 方法提交的 Runnable 物件會儲存在這裡。
- threadFactory : 執行緒工廠,為執行緒池提供建立新執行緒的功能。
AsyncTask 中的執行緒池 THREAD_POOL_EXECUTOR
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); // 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; }
從上面的程式碼可知,THREAD_POOL_EXECUTOR 配置後的規格如下:
- 核心執行緒數 最小為 2, 最大為 4
- 最大執行緒數為 CPU 核心數的 2 倍 + 1
- 超時時間為 30s ,且同樣作用於 核心執行緒
- 任務佇列的容量為 128
參考
Android 開發藝術探索