主頁 > 移動端開發 > Android進階知識——Android執行緒和執行緒池

Android進階知識——Android執行緒和執行緒池

2021-06-09 22:32:32 移動端開發

文章目錄

  • 1.主執行緒和子執行緒
  • 2.Android中的執行緒形態
    • 2.1AsyncTask
    • 2.2AsyncTask的作業原理
    • 2.3HandlerThread
    • 2.4IntentService
  • 3.Android中的執行緒池
    • 3.1ThreadPoolExecutor
    • 3.2執行緒池的分類

本章的主題是Android中的執行緒和執行緒池,執行緒在Android中是一個很重要的概念,從用途上來說,執行緒分為主執行緒和子執行緒,主執行緒主要處理和界面相關的事,而子執行緒則往往用于執行耗時操作,除了Thread本身以外,在Android中可以扮演執行緒角色的還有很多,比如AsyncTask和IntentService,同時HandlerThread也是一種特殊的執行緒,盡管AsyncTask、IntentService以及HandlerThread的表現形式都有別于傳統的執行緒,但是它們的本質仍然是傳統的執行緒,對于AsyncTask來說,它的底層用到了執行緒池,對于IntentService和HandlerThread來說,它們的底層則直接使用了執行緒,

不同形的執行緒雖然都是執行緒,但是它們仍然具有不同的特性和使用場景,AsyncTask封裝了執行緒池和Handler,它主要是為了方便開發者在子執行緒中更新UI,HandlerThread是一種具有訊息回圈的執行緒,在它內部可以使用Handler,IntentService內部采用HandlerThread來執行任務,當任務執行完畢之后IntentService會自動退出,從任務執行的角度來看,IntentService的作用更像是一個后臺執行緒,但是IntentService是一種服務,它不容易被系統殺死從而可以盡量保證任務的執行,而如果是一個后臺執行緒,由于這個時候行程中沒有活動的四大組件,那么這個行程的優先級就會非常低,會很容易被系統殺死,這就是IntentService的優點,(當行程中沒有活動的四大組件時,會很容易被系統殺死)

在作業系統中,執行緒是作業系統調度的最小單元,同時執行緒又是一種受限的系統資源、即執行緒不可能無限制地產生,并且執行緒的創建和銷毀都會有相應的開銷,當系統中存在大量的執行緒時,系統會通過時間片輪轉的方式調度每個執行緒,因此執行緒不可能做到絕對的并行,除非執行緒數量小于等于CPU的核心數,一般來說這是不可能的,試想一下,如果在一個行程中頻繁地創建和銷毀執行緒,這顯然不是高效的做法,正確的做法是采用執行緒池,一個執行緒池中會快取一定數量的執行緒,通過執行緒池就可以避免因為頻繁創建和銷毀執行緒所帶來的系統開銷,

1.主執行緒和子執行緒

主執行緒是指行程所擁有的執行緒,在java中默認情況下一個行程只有一個執行緒,這個執行緒就是主執行緒,主執行緒主要處理界面互動相關的邏輯,因為用戶隨時會和界面發生互動,因此主執行緒在任何時候都必須有較高的回應速度,否則就會產生一種界面卡頓的感覺,為了保持較高的回應速度,這就要求主執行緒中不能執行耗時任務,這個時候子執行緒就派上用場了,子執行緒也叫作業執行緒,除了主執行緒以外的執行緒都是子執行緒,

Android沿用了java的執行緒模型,其中執行緒也分為主執行緒和子執行緒,其中主執行緒也叫UI執行緒,主執行緒的作用是運行四大組件以及處理它們和用戶的互動,而子執行緒的作用則是執行耗時任務,比如網路請求、I/O操作等,從Android3.0開始系統要求網路訪問必須在子執行緒中進行,否則網路訪問將會失敗并拋出NetworkOnMainThreadException這個例外,這樣做是為了避免主執行緒由于被耗時操作所阻塞而出現ANR現象,

2.Android中的執行緒形態

本節將對Android中的執行緒形態做一個全面的介紹,除了傳統的Thread以外,還包含AsyncTask、HandlerThread以及IntentService,這三者的底層實作也是執行緒,但是它們具有特殊的表現形式,同時在使用上也各有優缺點,為了簡化在子執行緒中訪問UI的程序,系統提供了AsyncTask,本節我們將詳細介紹使用AsyncTask時的注意事項,并從原始碼的角度來分析AsyncTask的執行程序,

2.1AsyncTask

AsyncTask是一種輕量級的異步任務類,它可以在執行緒池中執行后臺任務,然后把執行的進度和最終結果傳遞給主執行緒并在主執行緒中去更新UI,從實作上來說,AsyncTask封裝了Thread和Handler,通過AsyncTask可以更加方便地執行后臺任務以及在主執行緒中訪問UI,但是AsyncTask并不適合進行特別耗時的后臺任務,對于特別耗時的任務來說,建議使用執行緒池,

AsyncTask是一個抽象的泛型類,它提供了Params、Progress和Result這三個泛型引數,其中Params表示引數的型別,Progress表示后臺任務的執行進度的型別,而Result則表示后臺任務的回傳結果的型別,如果AsyncTask確實不需要傳遞具體的引數,那么這三個泛型引數可以用Void來代替,AsyncTask這個類的宣告如下所示,

public abstract class AsyncTask<Params, Progress, Result>

AsyncTask提供了4個核心方法,它們的含義如下所示:

  • onPreExecute(),在主執行緒中執行,在異步任務執行之前,此方法會被呼叫,一般可以用于做一些準備作業,

  • doInBackground(Params…params),在執行緒池中執行,此方法用于執行異步任務,params引數表示異步任務的輸入引數,此方法中可以通過publishProgress方法來更新任務的進度,publishProgress方法會呼叫onProgressUpdate方法,另外此方法需要回傳計算結果給onPostExecute方法,

  • onProgressUpdate(Progress…values),在主執行緒中執行,當后臺任務的進度發生改變時此方法會被呼叫,

  • onPostExecute(Result result),在主執行緒中執行,在異步任務執行完畢后,此方法會被呼叫,其中result引數是后臺任務的回傳值,即doInBackground的回傳值,

上面這幾個方法,onPreExecute先執行,接著是doInBackground,最后才是onPostExecute,除了上述四個方法以外,AsyncTask還提供了onCancelled()方法,它同樣在主執行緒中執行,當異步任務被取消時,onCanceled()方法會被呼叫,這個時候onPostExecute則不會被呼叫,下面提供一個典型示例,如下所示:

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
	protected Long doInBackground(URL... urls) {
		int count = urls.length; 
		long totalSize = 0;
		for (int i=0; i< count; i++) {
			totalSize += Downloader.downloadFile(urls[i]);
			publishProgress((int) ((i /(float) count) * 100));
			if (isCancelled())
				break;
		}
		return totalSize;
	}
	
	protected void onProgressUpdate (Integer... progress) {
		setProgressPercent(progress[0]);
	}
	
	protected void onPostExecute (Long result) {
		showDialog ("Downloaded" + result + "bytes");
	}

在上面的代碼中,實作了一個具體的AsyncTask類,這個類主要用于模擬檔案下載的程序,它的輸入引數型別為URL,后臺任務的行程引數為Integer,而后臺任務的回傳值結果為Long型別,注意到doInBackground和onProgressUpdate方法它們的引數中均包含…的樣,在Java中…表示引數的數量不定,它是一種陣列型引數,…的概念和C語言中的…是一致的,當要執行上述下載任務時,可以通過如下方式完成:

new DownloadFilesTask().execute(url1,  ur12, ur13);

在DownloadFilesTask中,diInBackground用來執行具體的下載任務并通過publishProgress方法來更新下載的進度,同時還要判斷下載任務是否被外界取消了,當下載任務完成后,doInBackground后回傳結果,即下載的總位元組數,需要注意的是,doInBackground是在執行緒池中執行的,onProgressUpdate用于更新界面中下載的進度,它運行在主執行緒,當publishProgress被呼叫時,此方法就會被呼叫,當下載完成后,onPostExecute方法就會被呼叫,它也是運行在主執行緒中的,這個時候我們就可以在界面上做出一些提示,比如彈出一個對話框告知用戶下載已完成,

AsyncTask在具體的使用程序中也是有一些條件限制的,主要有如下幾點:

  • AsyncTask的類必須在主執行緒中加載,這就意味著第一次訪問AsyncTask必須發生在主執行緒,當然這個程序在Android4.1及以上版本中已經被系統自動完成,在Android5.0的原始碼中,可以查看ActivityThread的main方法,它會呼叫AsyncTask的init方法,這就滿足了AsyncTask的類必須在主執行緒中進行加載這個條件了,

  • AsyncTask的物件必須在主執行緒中創建,

  • execute方法必須在UI執行緒呼叫,

  • 不要在程式中直接呼叫onPreExecute()、onPreExecute、doInBackground和onProgressUpdate方法,

  • 一個AsyncTask物件只能執行一次,即只能呼叫一次execute方法,否則會報運行時例外,

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

2.2AsyncTask的作業原理

為了分析AsyncTask的作業原理,我們從它的execute方法開始分析,execute方法又會呼叫executeOnExecutor方法,它們的實作如下所示:

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

在上面的代碼中,sDefaultExecutor實際上是一個串行的執行緒池,一個行程中所有的AsyncTask全部在這個串行的執行緒池中排隊執行,在executeOnExecutor方法中,AsyncTask的onPreExecute方法最先執行,然后執行緒池開始執行,下面分析執行緒池的執行程序,如下所示:

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

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.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方法去處理,SerialExecutor的execute方法首先會把FutureTask物件插入到任務佇列mTasks中,如果這個時候沒有正在活動的AsyncTask任務,那么就會呼叫SerialExecutor的scheduleNext方法來執行下一個AsyncTask任務,同時當一個AsyncTask任務執行完后,AsyncTask會繼續執行其他任務直到所有的任務都被執行為止,從這一點可以看出,在默認情況下,AsyncTask是串行執行的,

AsyncTask中有兩個執行緒池(SerialExecutor和THREAD_POOL_EXECUTOR)和一個Handler(InternalHandler),其中執行緒池SerialExecutor用于任務的排隊,而執行緒池THREAD_POOL_EXECUTOR用于真正地執行任務,InternalHandler用于將執行環境從執行緒池切換到主執行緒,在AsyncTask的構造方法中有如下這么一段代碼,由于FutureTask的run方法會呼叫mWorker的call方法,因此mWorker的call方法最侄訓在執行緒池中執行,

mWorker = new WorkerRunnable<Params, Result>() {
	public Result call() throws Exception {
		mTaskInvoked.set(true);
		
		Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
		return postResult(doInBackground(mParams));
	}
};

在mWorker的call方法中,首先將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;
}

在上面的代碼中,postResult方法會通過sHandler發送一個MESSAGE_POST_RESULT的訊息,這個sHandler的定義如下所示:

private static final InternalHandler sHandler = new InternalHandler();

private static class InternalHandler extends Handler {
	@SuppressWarnings({ "unchecked", "RawUseOfParameterizedType" })
	@Override
	public void handleMessage(Message msg) {
		AsyncTaskResult result = (AsyncTaskResult) msg.obj;
		switch (msg.what) {
			case MESSAGE_POST_RESULT:
				result.mTask.finish(result.mData[0]);
				break;
			case MESSAGE_POST_PROGRESS:
				result.mTask.onProgressUpdate(result.mData);
				break;
		}
	}
}

我們可以發現,sHandler是一個靜態的Handler物件,為了能夠將執行環境切換到主執行緒,這就要求sHandler這個物件必須在主執行緒中創建,由于靜態成員會在加載類的時候進行初始化,因此這就變相要求AsyncHandler的類必須在主執行緒中加載,否則同一個行程中的AsyncTask都將無法正常作業,sHandler收到MESSAGE_POST_RESULT這個訊息后會呼叫AsyncTask的finish方法,如下所示:

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

AsyncTask的finish方法的邏輯比較簡單,如果AsyncTask被取消執行了,那么就呼叫onCancelled方法,否則就會呼叫onPostExecute方法,可以看到doInBackground的回傳結果會傳遞給onPostExecute方法,到這里AsyncTask的整個作業程序就分析完畢了,

通過分析AsyncTask的原始碼,可以進一步確定,從Android3.0開始,默認情況下AsyncTask的確是串行執行的,如果你想要AsyncTask在Android3.0及以上的版本上并行,可以采用AsyncTask的executeOnExecutor方法(需要注意的是這個方法是Android3.0新添加的方法,并不能在低版本上使用),

2.3HandlerThread

HandlerThread繼承了Thread,它是一種可以使用Handler的Thread,它的實作也很簡單,就是在run方法中通過Looper.prepare()來創建訊息佇列,并通過Looper.loop()來開且訊息回圈,這樣在實際的使用中就允許在HandlerThread中創建Handler了,HandlerThread的run方法如下所示:

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

從HandlerThread的實作來看,它和普通的Thread有顯著的不同之處,普通Thread主要用于在run方法中執行一個耗時任務,而HandlerThread在內部創建了訊息佇列,外界需要通過Handler的訊息方式來通知HandlerThread執行一個具體的任務,由于HandlerThread的run方法是一個無限回圈,因此當明確不需要再使用HandlerThread時,可以通過它的quit或者quitSafely方法來終止執行緒的執行,這是一個良好的編程習慣,

2.4IntentService

IntentService可用于執行后臺耗時的任務,當任務執行后它會自動停止,同時由于IntentService是服務的原因,這導致它的優先級比單純的執行緒要高得多,所以IntentService比較適合執行一些高優先級的后臺任務,因為它優先級高不容易被系統殺死,在實作上,IntentService封裝了HandlerThread和Handler,這一點可以從它的onCreate方法中看出來,如下所示:

public void onCreate() {
	super.onCreate();
	HandlerThread thread = new HandlerThread("IntentService [" + mName + "]");
	thread.start();
	
	mServiceLooper = thread.getLooper();
	mServiceHandler = new ServiceHandler(mServiceLooper);
}

當IntentService被第一次啟動時,它的onCreate方法會被呼叫,onCreate方法會創建一個HandlerThread,然后使用它的Looper來構造一個Handler物件mServiceHandler,這樣通過mServiceHandler發送的訊息最終都會在HandlerThread中執行,從這個角度來看,IntentService也可以用于執行后臺任務,每次啟動IntentService,它的onStartCommand方法就會呼叫一次,IntentService在inStartCommand中處理每個后臺任務的Intent,下面我們看一下onStartCommand方法是如何處理外界的Intent的,onStartCommand呼叫了onStart,onStart方法的實作如下所示:

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

IntentService僅僅是通過mServiceHandler發送了一個訊息,這個訊息會在HandlerThread中被處理,mServiceHandler收到訊息后,會將Intent物件傳遞給onHandlerIntent方法去處理,注意這個Intent物件的內容和外界的startService(intent)中的intent的內容是完全一致的,通過這個Intent物件即可決議出外界啟動IntentService時所傳遞的引數,通過這些引數就可以區分具體的后臺任務,這樣在onHandlerIntent方法中就可以對不同的后臺任務做處理了,當onHandlerIntent方法執行結束后,IntentService會通過stopSelf(int startId)方法來嘗試停止服務,這里之所以采用stopSelf(int startId)而不是stopSelf()來停止服務,是因為stopSelf()會立刻停止服務,而這個時候可能還有其他訊息未處理,stopSelf(int startId)則會等待所有的訊息都處理完畢后才終止服務,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的onHandlerIntent方法是一個抽象方法,它需要我們在子類中實作,它的作用是從Intent引數中區分具體的任務并執行這些任務,如果目前只存在一個后臺任務,那么onHandlerIntent方法執行完這個任務后,stopSelf(int startId)就會直接停止服務;如果目前存在多個后臺任務,那么當onHandlerIntent方法執行完最后一個任務時,stopSelf(int startId)才會直接停止服務,另外,由于每執行一個后臺任務就必須啟動一次IntentService,而IntentService內部則通過訊息的方式向HandlerThread請求執行任務,Handler中的Looper是順序處理訊息的,這就意味著IntentService也是順序執行后臺任務的,當有多個后臺任務同時存在時,這些后臺任務會按照外界的發起順序排隊執行,

下面通過一個示例來進一步說明IntentService的作業方式,首先派生一個IntentService的子類,比如LocalIntentService,它的實作如下所示:

public class LocalIntentService extends IntentService {
	private static final String TAG = "LocalIntentService";
	
	public LocalIntentService() {
		super(TAG);
	}
	
	@Override
	protected void onHandleIntent(Intent intent) (
		String action = intent. getStringExtra("task_action");
		Log.d(TAG,"receive task :" + action);
		SystemClock.sleep(3000);
		if ("com.ryg.action.TASK1".equals(action)) {
			Log.d(TAG, "handle task:" + action);
		}
	}
	
	@Override
	public void onDestroy(){
		Log.d(TAG, "service destroyed.");
		super.onDestroy();
	}
}

在onHandlerIntent方法會從引數中決議出后臺任務的標識,即task_action欄位所代表的內容,然后根據不同的任務標識來執行具體的后臺任務,這里為了簡單起見,直接通過SystemClock.sleep(3000)來休眠3000毫秒從而模擬一種耗時的后臺任務,另外為了驗證IntentService的停止時機,這里在onDestory()中列印了一句日志,LocalIntentService實作完成了以后,就可以在外界請求執行后臺任務了,在下面的代碼中先后發起了3個后臺任務請求:

Intent service = new Intent(this, LocalIntentService.class);
service.putExtra("task_action", "com.ryg.action.TASK1");
startService(service);
service.putExtra("task_action", "com.ryg.action.TASK2");
startService(service);
service.putExtra("task_action", "com.ryg.action.TASK3");
startService(service);

運行程式,觀察日志,如下所示:

05-17 17:08:23.186 E/dalvikvm(25793): threadid=11: calling run (), name=IntentService [Local IntentService]
05-17 17:08:23.196 D/LocalIntentService(25793): receive task :com.ryg.action.TASK1
05-17 17:08:26.199 D/LocalIntentService(25793): handle task: com.ryg.action.TASK1
05-17 17:08:26.199 D/LocalIntentService(25793): receive task : com.ryg.action.TASK2
05-17 17:08:29.192 D/LocalIntentService(25793): receive task :com.ryg.action.TASK3
05-17 17:08:32.205 D/LocalIntentervice(25793): service destroyed.
05-17 17:08:32 .205 E/dalvikvm(25793): threadid-11: exiting, name=IntentService [LocalIntentService]

從上面的日志可以看出,三個后臺任務是排隊執行的,它們的執行順序就是它們發起請求的順序,即TASK1、TASK2、TASK3,另外一點就是當TASK3執行完畢后,LocalIntentService才真正地停止,從日志中可以看出LocalIntentService執行了onDestory(),這也就意味著服務正在停止,

3.Android中的執行緒池

提到執行緒池就必須先說一下執行緒池的好處,相信讀者都有所體會,執行緒池的優點可以概括為以下三點:

  • 重用執行緒池中的執行緒,避免應為執行緒的創建和銷毀所帶來的性能開銷

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

  • 能夠對執行緒進行簡單的管理,并提供定時執行以及指定間隔回圈執行等功能

Android中的執行緒池的概念來源于Java中的Executor,Executor是一個介面,真正的執行緒池的實作為ThreadPoolExecutor,ThreadPoolExecutor提供了一系列引數來配置執行緒池,通過不同的引數可以創建不同的執行緒池,從執行緒池的功能特性上來說,Android的執行緒池主要分為4類,這4類執行緒池可以通過Executors所提供的工廠方法來得到,由于Android中的執行緒池都是直接或間接通過配置ThreadPoolExecutor來實作的,因此在介紹它們之前需要先介紹ThreadPoolExecutor,

3.1ThreadPoolExecutor

ThreadPoolExecutor是執行緒池的真正實作,它的構造方法提供了一系列引數來配置執行緒池,下面介紹ThreadPoolExecutor的構造方法中各個引數的含義,這些引數將會直接影響到執行緒池的功能特性,下面是ThreadPoolExecutor的一個比較常用的構造方法:

public ThreadPoolExecutor (int corePoolSize,
					int maximumPoolSize,
					long keepAliveTime, 
					TimeUnit unit,
					BlockingQueue<Runnable> workQueue,
					ThreadFactory threadFactory)
  • corePoolSize

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

  • maximumPoolSize

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

  • keepAliveTime

非核心執行緒閑置的超時時長,超過這個時長,非核心執行緒就會被回收,當ThreadPoolExecutor的allowCoreThreadTimeOut屬性設定為true時,keepAliveTime同樣會作用于核心執行緒,

  • unit

用于指定keepAliveTime引數的時間單位,這是一個列舉,常用的有TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)以及TimeUnit.MINUTES(分鐘)等,

  • workQueue

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

  • ThreadFactory

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

除了上面的這些主要引數外,ThreadPoolExecutor還有一個不常用的引數RejectedExecutionHandler handler,當執行緒池無法執行新任務時,這可能是由于任務佇列已滿或者是無法成功執行任務,這個時候ThreadPoolExecutor會呼叫handler的rejectedExecution方法來通知呼叫者,默認情況下rejectedExecution方法會直接拋出一個RejectedExecutionException,ThreadPoolExecutor為RejectedExecutionHandler提供了幾個可選值:CallerRunsPolicy、AbortPolicy、DiscardPolicy和DiscardOldestPolicy,其中AbortPolicy是默認值,它會直接拋出RejectedExecutionException,由于handler這個引數不常用,這里就不再具體介紹了,

ThreadPoolExecutor執行任務時大致遵循如下規則(核心執行緒 > 任務佇列 > 非核心執行緒):

(1) 如果執行緒池中的核心執行緒數量未達到核心執行緒的數量,那么會直接啟動一個核心執行緒來執行任務,

(2) 如果執行緒池中的執行緒數量已經達到或者超過核心執行緒的數量,那么任務會被插入到任務佇列中排隊等待執行,

(3) 如果在步驟2中無法將任務插入到任務佇列中,這往往是由于任務佇列已滿,這個時候如果執行緒數量未達到執行緒池規定的最大值,那么就會立刻啟動一個非核心執行緒來執行任務,

(4) 如果步驟3中執行緒數量已經達到執行緒池規定的最大值,那么就拒絕執行此任務,ThreadPoolExecutor會呼叫RejectedExecutionHandler的rejectedExecution方法來通知呼叫者,

ThreadPoolExecutor的引數配置在AsyncTask中有明顯的體現,下面是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);
	
public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

從上面的代碼可以知道,AsyncTask對THREAD_POOL_EXECUTOR這個執行緒池進行了配置,配置后的執行緒池規格如下:

  • 核心執行緒數等于CPU核心數 + 1;

  • 執行緒池的最大執行緒數為CPU核心數的2倍 + 1;

  • 核心執行緒無超時機制,非核心執行緒在閑置時的超時時間為1秒;

  • 任務佇列的容量為128,

3.2執行緒池的分類

在上一節中我們對ThreadPoolExecutor的配置細節進行了詳細的介紹,本節將接著介紹Android中最常見的四類具有不同功能特性的執行緒池,它們都直接或間接地通過配置ThreadPoolExecutor來實作自己的功能特性,這四類執行緒池分別為FixedThreadPool、CachedThreadPool、ScheduledThreadPool以及SingleThreadExecutor,

1.FixedThreadPool

通過Executors的newFixedThreadPool方法來創建,它是一種執行緒數量固定的執行緒池,當執行緒處于空閑狀態時,它們并不會被回收,除非執行緒池被關閉了,當所有的執行緒都處于活動狀態時,新任務都會處于等待狀態,直到有執行緒空閑出來,由于FixedThreadPool只有核心執行緒并且這些核心執行緒并不會被回收,這就意味著它能夠更快地回應外界的請求,newFixedThreadPool方法的實作如下,可以發現FixedThreadPool中只有核心執行緒并且這些核心執行緒沒有超時機制,另外任務佇列也是沒有大小限制的,

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

2.CachedThreadPool

通過Executors的newCachedThreadPool方法來創建,它是一個執行緒數量不定的執行緒池,它只有非核心執行緒,并且最大執行緒數為Integer.MAX_VALUE,由于Integer.MAX_VALUE是一個很大的數,實際上就相當于最大執行緒數可以任意大,當執行緒中的執行緒都處于活動狀態時,執行緒池會創建新的執行緒來處理新任務,否則就會利用空閑執行緒來處理新任務,執行緒池中的空閑執行緒都有超時機制,這個時長為60秒,超過60秒閑置執行緒就會被回收,和FixedThreadPool不同的是,CachedThreadPool的任務佇列其實相當于一個空集合,這將導致任何任務都會立即被執行,因為在這種情況下SynchronousQueue是無法插入任務的,SynchronousQueue是一個非常特殊的佇列,在很多情況下可以把它簡單理解為一個無法存盤元素的佇列,從CachedThreadPool的特性來看,這類執行緒比較適合執行大量的耗時較少的任務,當整個執行緒池都處于閑置狀態時,執行緒池中的執行緒都會超時而被停止,這個時候CachedThreadPool之中實際上是沒有任何執行緒的,它幾乎是不占用任何系統資源的,newCachedThreadPool方法的實作如下所示:

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

3.ScheduledThreadPool

通過Executors的newScheduledThreadPool方法來創建,它的核心執行緒數量是固定的,而非核心執行緒數是沒有限制的,并且當非核心執行緒閑置時會被立即回收,ScheduledThreadPool這類執行緒池主要用于執行定時任務和具有固定周期的重復任務,newScheduledThreadPool方法的實作如下:

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

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

4.SingleThreadExecutor

通過Executors的newSingleThreadExecutor方法來創建,這類執行緒池內部只有一個核心執行緒,它確保所有的任務都在同一執行緒中按順序執行,SingleThreadExecutor的意義在于統一所有的外界任務到一個執行緒中,這使得在這些任務之間不需要處理執行緒同步問題,newSingleThreadExecutor方法的實作如下所示:

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

上面對Android中常見的4種執行緒池進行了詳細的介紹,除了上面系統提供的4類執行緒池外,也可以根據實際需要靈活地配置執行緒池,下面的代碼演示了系統預置的4種執行緒池的典型使用方法:

Runnable command = new Runnable () {
	@Override
	public void run() {
		SystemClock.sleep(2000);
	}
}

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4);
fixedThreadPool.execute(command);

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
cachedThreadPool.execute(command);

ScheduledExecutorServices cheduledThreadPool = Executors.newScheduledThreadPool(4);//善于執行定時任務及具有固定周期的重復任務
scheduledThreadPool.schedule(command, 2000, TimeUnit.MILLISECONDS);//2000ms后執行command
scheduledThreadPool.scheduleAtFixedRate(command, 10, 100, TimeUnit.MILLI_SECONDS);//延遲10ms后,每隔1000ms執行一次command

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
singleThreadExecutor.execute(command);

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/286355.html

標籤:其他

上一篇:【12】Kotlin函式泛型協程

下一篇:android 仿微信小demo(實作移動端,服務端)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more