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_EXECUTOR 和 THREAD_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
通過 Executors
的 newScheduledThreadPool
方法來建立,它的核心執行緒數是固定的。而非核心執行緒數是沒有限制的。 非核心執行緒的超時策略中 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
通過 Executors
的 newSingleThreadExecutor
方法來建立。這個執行緒池內部只有一個核心執行緒。並且最大執行緒數也是1 。它確保所有的任務都在同一個執行緒中按順序執行。 SingleThreadExecutor
在於統一外界所有的任務到一個執行緒中。使得這些任務不需要處理同步的問題。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
到這執行緒池的相關內容就講完了。