Advanced knowledge of Android - Android threads and thread pools

Posted by AV1611 on Tue, 01 Feb 2022 23:12:06 +0100


The topic of this chapter is threads and thread pools in Android. Thread is a very important concept in Android. In terms of purpose, thread is divided into main thread and sub thread. The main thread mainly deals with things related to the interface, while sub thread is often used to perform time-consuming operations. In addition to the thread itself, there are many threads that can play the role in Android, such as AsyncTask and IntentService. At the same time, HandlerThread is also a special thread. Although the expressions of AsyncTask, IntentService and HandlerThread are different from traditional threads, their essence is still traditional threads. For AsyncTask, its bottom layer uses thread pool, while for IntentService and HandlerThread, their bottom layer directly uses threads.

Although threads of different shapes are threads, they still have different characteristics and usage scenarios. AsyncTask encapsulates thread pool and Handler. It is mainly to facilitate developers to update UI in sub threads. HandlerThread is a thread with message loop, and Handler can be used inside it. IntentService uses HandlerThread to execute tasks internally. When the task is completed, IntentService will exit automatically. From the perspective of task execution, IntentService is more like a background thread, but IntentService is a service, which is not easy to be killed by the system, so it can ensure the execution of tasks as much as possible. If it is a background thread, because there are no four active components in the process at this time, the priority of the process will be very low, It can be easily killed by the system, which is the advantage of IntentService. (when there are no four active components in the process, it will be easily killed by the system)

In the operating system, thread is the smallest unit of operating system scheduling. At the same time, thread is a limited system resource, that is, threads cannot be generated indefinitely, and the creation and destruction of threads will have corresponding overhead. When there are a large number of threads in the system, the system will schedule each thread through time slice rotation. Therefore, threads cannot be absolutely parallel, unless the number of threads is less than or equal to the number of CPU cores, which is generally impossible. Imagine that it is obviously not efficient to create and destroy threads frequently in a process. The correct approach is to adopt thread pool. A certain number of threads will be cached in a thread pool. Through thread pool, the system overhead caused by frequent creation and destruction of threads can be avoided.

1. Main thread and sub thread

The main thread refers to the thread owned by the process. In java, by default, a process has only one thread, which is the main thread. The main thread mainly deals with the logic related to interface interaction, because the user will interact with the interface at any time, so the main thread must have a high response speed at any time, otherwise it will have a feeling of interface jamming. In order to maintain a high response speed, it requires that the main thread cannot perform time-consuming tasks, and the sub thread comes in handy. Sub threads are also called worker threads. All threads except the main thread are sub threads.

Android follows the java thread model, in which threads are also divided into main threads and sub threads, and the main thread is also called UI thread. The main thread is used to run the four components and handle their interaction with users, while the sub thread is used to perform time-consuming tasks, such as network requests, I/O operations, etc. From Android 3 0. The system requires that the network access must be carried out in the sub thread, otherwise the network access will fail and the exception NetworkOnMainThreadException will be thrown. This is to avoid the ANR phenomenon caused by the main thread being blocked by time-consuming operations.

2. Thread form in Android

This section will give a comprehensive introduction to the Thread form in Android. In addition to the traditional Thread, it also includes AsyncTask, HandlerThread and IntentService. The underlying implementation of these three is also Thread, but they have special manifestations and have their own advantages and disadvantages in use. In order to simplify the process of accessing the UI in the sub Thread, the system provides AsyncTask. In this section, we will introduce the precautions when using AsyncTask in detail, and analyze the execution process of AsyncTask from the perspective of source code.

2.1AsyncTask

AsyncTask is a lightweight asynchronous task class. It can execute background tasks in the Thread pool, then pass the execution progress and final results to the main Thread, and update the UI in the main Thread. In terms of implementation, AsyncTask encapsulates Thread and Handler, which makes it easier to execute background tasks and access UI in the main Thread. However, AsyncTask is not suitable for special time-consuming background tasks. For special time-consuming tasks, it is recommended to use Thread pool.

AsyncTask is an abstract generic class. It provides three generic parameters: params, Progress and Result. Params represents the type of parameter, Progress represents the type of execution Progress of background task, and Result represents the type of returned Result of background task. If AsyncTask really does not need to pass specific parameters, Then these three generic parameters can be replaced by Void. The AsyncTask class is declared as follows.

public abstract class AsyncTask<Params, Progress, Result>

AsyncTask provides four core methods. Their meanings are as follows:

  • onPreExecute() is executed in the main thread. This method will be called before the asynchronous task is executed. It can generally be used to do some preparatory work.

  • doInBackground(Params... params), which is executed in the thread pool. This method is used to execute asynchronous tasks. Params parameter represents the input parameters of asynchronous tasks. In this method, the progress of the task can be updated through the publishProgress method, which will call the onProgressUpdate method. In addition, this method needs to return the calculation result to the onPostExecute method.

  • onProgressUpdate(Progress... values) is executed in the main thread. This method will be called when the progress of background tasks changes.

  • Onpostexecute (result) is executed in the main thread. After the asynchronous task is executed, this method will be called. The result parameter is the return value of the background task, that is, the return value of doInBackground.

For the above methods, onPreExecute is executed first, then doInBackground, and finally onPostExecute. In addition to the above four methods, AsyncTask also provides the onCancelled() method, which is also executed in the main thread. When the asynchronous task is cancelled, the onCancelled() method will be called, and onPostExecute will not be called at this time. A typical example is provided below, as follows:

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

In the above code, a specific AsyncTask class is implemented. This class is mainly used to simulate the process of file download. Its input parameter type is URL, the process parameter of background task is Integer, and the return value result of background task is Long. Note that the parameters of doInBackground and onProgressUpdate methods contain the sample of... In Java... Indicates that the number of parameters is uncertain. It is an array parameter, and the concept of... Is consistent with that of... In C language. When you want to perform the above download task, you can complete it in the following ways:

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

In DownloadFilesTask, diInBackground is used to execute specific download tasks, update the download progress through the publishProgress method, and judge whether the download task has been cancelled by the outside world. When the download task is completed, the result is returned after doInBackground, that is, the total number of bytes downloaded. Note that doInBackground is executed in the thread pool. onProgressUpdate is used to update the download progress in the interface. It runs in the main thread. When publishProgress is called, this method will be called. After the download is completed, the onPostExecute method will be called, which is also running in the main thread. At this time, we can make some prompts on the interface, such as pop-up a dialog box to inform the user that the download has been completed.

There are also some restrictions on the specific use of AsyncTask, mainly as follows:

  • The AsyncTask class must be loaded in the main thread, which means that the first access to AsyncTask must occur in the main thread. Of course, this process is in Android 4 1 and above versions have been automatically completed by the system. On Android 5 In the source code of 0, you can view the main method of ActivityThread, which will call the init method of AsyncTask, which meets the condition that AsyncTask's class must be loaded in the main thread.

  • The object of AsyncTask must be created in the main thread.

  • The execute method must be called on the UI thread.

  • Do not call onPreExecute(), onPreExecute, doInBackground, and onProgressUpdate methods directly in your program.

  • An AsyncTask object can only be executed once, that is, the execute method can only be called once, otherwise a runtime exception will be reported.

  • On Android 1 Before 6, AsyncTask executed tasks serially, Android 1 At 6, AsyncTask began to use thread pool to process parallel tasks, but Android 3 0. In order to avoid concurrency errors caused by AsyncTask, AsyncTask uses another thread to execute tasks serially. Still, in Android 3 0 and later versions, we can still execute tasks in parallel through the executeOnExecutor method of AsyncTask.

2.2 working principle of asynctask

In order to analyze the working principle of AsyncTask, we start with its execute method, which will call the executeOnExecutor method. Their implementation is as follows:

public final AsyncTask<Params, Progress, Result> execute(Params... params) {
	return executeOnExecutor(sDefaultExecutor, params);//Serial thread pool
}

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

In the above code, sdefaultexecution is actually a serial thread pool. All asynctasks in a process are queued for execution in this serial thread pool. In the executeOnExecutor method, the onPreExecute method of AsyncTask is executed first, and then the thread pool starts to execute. The execution process of thread pool is analyzed as follows:

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

From the implementation of SerialExecutor, we can analyze the queued execution process of AsyncTask. First, the system will encapsulate the Params parameter of AsyncTask as FutureTask. FutureTask is a concurrent class, which acts as Runnable here. Then the FutureTask will be handed over to the execute method of the SerialExecutor. The execute method of the SerialExecutor will first insert the FutureTask object into the task queue mTasks. If there is no active AsyncTask task at this time, the scheduleNext method of the SerialExecutor will be called to execute the next AsyncTask task. At the same time, after an AsyncTask task is executed, AsyncTask will continue to execute other tasks until all tasks are executed. It can be seen from this that AsyncTask is executed serially by default.

AsyncTask has two thread pools (SerialExecutor and THREAD_POOL_EXECUTOR) and one Handler(InternalHandler). The thread pool SerialExecutor is used for task queuing, while the thread pool thread_ POOL_ Executor is used to actually execute tasks, and InternalHandler is used to switch the execution environment from the thread pool to the main thread. In the construction method of AsyncTask, there is the following code. Since the run method of FutureTask will call the call method of mWorker, the call method of mWorker will eventually be executed in the process pool.

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

In the call method of mWorker, first set mTaskInvoked to true, indicating that the current task has been called, then execute the doInBackground method of AsyncTask, and then pass its return value to the postResult method. Its implementation is as follows:

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

In the above code, the postResult method sends a message through the sHandler_ POST_ Result message. The definition of this sHandler is as follows:

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

We can find that sHandler is a static Handler object. In order to switch the execution environment to the main thread, sHandler must be created in the main thread. Because static members will be initialized when loading classes, this requires that the class of AsyncHandler must be loaded in the main thread, otherwise AsyncTask in the same process will not work normally. sHandler received message_ POST_ After the message result, the finish method of AsyncTask will be called, as shown below:

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

The logic of the finish method of AsyncTask is relatively simple. If the execution of AsyncTask is canceled, call the onCancelled method. Otherwise, call the onPostExecute method. You can see that the return result of doInBackground will be passed to the onPostExecute method. Here, the whole working process of AsyncTask is analyzed.

By analyzing the source code of AsyncTask, it can be further determined from Android 3 Starting from 0, AsyncTask is indeed executed serially by default. If you want AsyncTask in Android 3 0 and above, the executeOnExecutor method of AsyncTask can be used (note that this method is a new method added by Android 3.0 and cannot be used in lower versions).

2.3HandlerThread

HandlerThread inherits from Thread. It is a Thread that can use Handler. Its implementation is also very simple, that is, through looper. In the run method Prepare() to create a message queue and use looper Loop () to open and loop the message, so that in actual use, it is allowed to create a Handler in the HandlerThread. The run method of HandlerThread is as follows:

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

From the implementation of HandlerThread, it is significantly different from ordinary Thread. The normal Thread is mainly used to execute a time-consuming task in the run method, while the HandlerThread creates a message queue internally. The outside world needs to notify the HandlerThread to execute a specific task through the Handler's message. Since the run method of HandlerThread is an infinite loop, when it is clear that HandlerThread does not need to be used anymore, the Thread execution can be terminated through its quit or quitsafe method, which is a good programming habit.

2.4IntentService

IntentService can be used to execute background time-consuming tasks. When the task is executed, it will automatically stop. At the same time, because IntentService is a service, its priority is much higher than that of a simple thread. Therefore, IntentService is more suitable for executing some high priority background tasks, because it has high priority and is not easy to be killed by the system. In terms of implementation, IntentService encapsulates HandlerThread and Handler, which can be seen from its onCreate method, as shown below:

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

When IntentService is started for the first time, its onCreate method will be called. onCreate method will create a HandlerThread, and then use its Looper to construct a Handler object mServiceHandler. In this way, messages sent through mServiceHandler will eventually be executed in HandlerThread. From this point of view, IntentService can also be used to execute background tasks. Every time IntentService is started, its onStartCommand method will be called once. Intent service processes the intents of each background task in inStartCommand. Let's see how onStartCommand handles external intents. onStartCommand calls onStart. The implementation of onStart method is as follows:

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

IntentService just sends a message through mServiceHandler, which will be processed in HandlerThread. After receiving the message, mServiceHandler will pass the intent object to onHandlerIntent method for processing. Note that the content of this intent object is completely consistent with the content of the intent in the external startService(intent). Through this intent object, the parameters passed when the external starts the IntentService can be resolved. Through these parameters, specific background tasks can be distinguished, so that different background tasks can be processed in the onHandlerIntent method. After the onHandlerIntent method is executed, IntentService will try to stop the service through the stopSelf(int startId) method. The reason why stopSelf(int startId) is used instead of stopSelf() to stop the service here is that stopSelf() will stop the service immediately, and there may be other messages unprocessed at this time. stopSelf(int startId) will wait for all messages to be processed before terminating the service. The implementation of ServiceHandler is as follows:

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

The onHandlerIntent method of IntentService is an abstract method, which needs to be implemented in subclasses. Its function is to distinguish specific tasks from Intent parameters and execute these tasks. If there is only one background task at present, after onHandlerIntent method executes this task, stopSelf(int startId) will directly stop the service; If there are multiple background tasks at present, the stopSelf(int startId) will stop the service directly when the onHandlerIntent method completes the last task. In addition, the IntentService must be started every time a background task is executed, and the IntentService requests the Handler thread to execute the task through the message. The Looper in the Handler processes the message in sequence, which means that the IntentService also executes the background tasks in sequence. When multiple background tasks exist at the same time, These background tasks will be queued and executed according to the order initiated by the outside world.

The following is an example to further illustrate the working mode of IntentService. First, derive a subclass of IntentService, such as LocalIntentService. Its implementation is as follows:

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

In the onHandlerIntent method, the identifier of the background task, i.e. task, will be parsed from the parameters_ The content represented by the action field, and then execute specific background tasks according to different task IDs. For simplicity, you can use systemclock Sleep (3000) to sleep for 3000 milliseconds to simulate a time-consuming background task. In addition, in order to verify the stop time of IntentService, a log is printed in onDestory(). After the implementation of LocalIntentService, you can request the execution of background tasks from the outside. In the following code, three background task requests have been initiated successively:

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

Run the program and observe the log as follows:

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]

It can be seen from the above log that the three background tasks are queued for execution, and their execution order is the order in which they initiate requests, that is, TASK1, TASK2 and TASK3. Another point is that the LocalIntentService really stops after TASK3 is executed. From the log, we can see that the LocalIntentService executes onDestory(), which means that the service is stopping.

3. Thread pool in Android

When it comes to thread pool, you must first talk about the benefits of thread pool. I believe readers will appreciate it. The advantages of thread pool can be summarized as follows:

  • Reuse the threads in the thread pool to avoid the performance overhead caused by the creation and destruction of threads

  • It can effectively control the significant concurrent number of thread pool and avoid the blocking phenomenon caused by a large number of threads seizing system resources

  • It can simply manage threads and provide functions such as timed execution and specified interval cyclic execution

The concept of thread pool in Android comes from the executor in Java. Executor is an interface. The implementation of real thread pool is ThreadPoolExecutor. ThreadPoolExecutor provides a series of parameters to configure thread pools. Different thread pools can be created through different parameters. In terms of the functional characteristics of thread pools, Android thread pools are mainly divided into four categories, which can be obtained through the factory methods provided by Executors. Since thread pools in Android are implemented directly or indirectly by configuring ThreadPoolExecutor, we need to introduce ThreadPoolExecutor before introducing them.

3.1ThreadPoolExecutor

ThreadPoolExecutor is the real implementation of thread pool. Its construction method provides a series of parameters to configure thread pool. The following describes the meanings of various parameters in the construction method of ThreadPoolExecutor. These parameters will directly affect the functional characteristics of thread pool. The following is a common construction method of ThreadPoolExecutor:

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

The number of core threads in the thread pool. By default, core threads will survive in the thread pool even if they are idle. If the allowCoreThreadTimeOut property of ThreadPoolExecutor is set to true, the idle core thread will have a timeout policy when waiting for the arrival of a new task. This time interval is specified by keepalivetime. When the waiting time exceeds the time specified by keepalivetime, the core thread will be terminated.

  • maximumPoolSize

The maximum number of threads that the thread pool can hold. When the number of active threads reaches this value, subsequent new tasks will be blocked.

  • keepAliveTime

The idle timeout length of the non core thread. If it exceeds this length, the non core thread will be recycled. When the allowCoreThreadTimeOut property of ThreadPoolExecutor is set to true, keepAliveTime will also act on the core thread.

  • unit

It is used to specify the time unit of the keepAliveTime parameter. This is an enumeration. The commonly used timeunit Milliseconds (MS), timeunit Seconds and timeunit Minutes, etc.

  • workQueue

For the task queue in the thread pool, the Runnable object submitted through the execute method of the thread pool will be stored in this parameter.

  • ThreadFactory

Thread factory, which provides the function of creating new threads for threads. ThreadFactory is an interface with only one method: Thread newThread(Runnable r).

In addition to the above main parameters, ThreadPoolExecutor also has an uncommon parameter RejectedExecutionHandler handler. When the thread pool cannot execute a new task, it may be because the task queue is full or the task cannot be executed successfully. At this time, the ThreadPoolExecutor will call the rejectedExecution method of the handler to notify the caller. By default, the rejectedExecution method will directly throw a RejectedExecutionException. ThreadPoolExecutor provides several optional values for RejectedExecutionHandler: CallerRunsPolicy, AbortPolicy, DiscardPolicy and DiscardOldestPolicy. AbortPolicy is the default value and will directly throw RejectedExecutionException. Since handler is not often used, it will not be described in detail here.

ThreadPoolExecutor generally follows the following rules when executing tasks (core thread > task queue > non core thread):

(1) If the number of core threads in the thread pool does not reach the number of core threads, a core thread will be directly started to execute the task.

(2) If the number of threads in the thread pool has reached or exceeded the number of core threads, the task will be inserted into the task queue and queued for execution.

(3) If the task cannot be inserted into the task queue in step 2, it is often because the task queue is full. At this time, if the number of threads does not reach the maximum specified by the thread pool, a non core thread will be started immediately to execute the task.

(4) If the number of threads in step 3 has reached the maximum specified by the thread pool, the task will be rejected, and the ThreadPoolExecutor will call the rejectedExecution method of RejectedExecutionHandler to notify the caller.

The parameter configuration of ThreadPoolExecutor is clearly reflected in AsyncTask. The following is the configuration of thread pool in 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);

From the above code, we can know that AsyncTask is effective for thread_ POOL_ The thread pool executor is configured. The thread pool specifications after configuration are as follows:

  • The number of core threads is equal to the number of CPU cores + 1;

  • The maximum number of threads in the thread pool is 2 times + 1 of the number of CPU cores;

  • The core thread has no timeout mechanism, and the timeout of non core thread when idle is 1 second;

  • The capacity of the task queue is 128.

3.2 classification of thread pool

In the previous section, we introduced the configuration details of ThreadPoolExecutor in detail. This section will then introduce the four most common types of thread pools with different functional characteristics in Android. They all realize their functional characteristics directly or indirectly by configuring ThreadPoolExecutor. These four types of thread pools are FixedThreadPool, CachedThreadPool ScheduledThreadPool and singlethreadexecution.

1.FixedThreadPool

It is created through the newFixedThreadPool method of Executors. It is a thread pool with a fixed number of threads. When threads are idle, they will not be recycled unless the thread pool is closed. When all threads are active, the new task will be in the waiting state until a thread is idle. Because FixedThreadPool has only core threads and these core threads will not be recycled, this means that it can respond to external requests faster. The implementation of the newFixedThreadPool method is as follows. It can be found that there are only core threads in the FixedThreadPool, and these core threads have no timeout mechanism. In addition, there is no size limit on the task queue.

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

2.CachedThreadPool

Created by the Executors' newCachedThreadPool method. It is a thread pool with an indefinite number of threads. It has only non core threads, and the maximum number of threads is integer MAX_ VALUE. Due to integer MAX_ Value is a large number, which is actually equivalent to the maximum number of threads. It can be as large as you want. When all threads in the thread are active, the thread pool will create new threads to process new tasks, otherwise idle threads will be used to process new tasks. All idle threads in the thread pool have a timeout mechanism. The duration is 60 seconds. If it exceeds 60 seconds, the idle threads will be recycled. Unlike FixedThreadPool, the task queue of CachedThreadPool is actually equivalent to an empty collection, which will cause any task to be executed immediately, because in this case, synchronous queue cannot insert tasks. Synchronous queue is a very special queue. In many cases, it can be simply understood as a queue that cannot store elements. From the characteristics of CachedThreadPool, this kind of thread is more suitable for performing a large number of tasks with less time-consuming. When the whole thread pool is idle, the threads in the thread pool will timeout and be stopped. At this time, there are actually no threads in the CachedThreadPool, which hardly occupies any system resources. The implementation of the newCachedThreadPool method is as follows:

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

3.ScheduledThreadPool

Created through the Executors' newScheduledThreadPool method. Its number of core threads is fixed, while the number of non core threads is unlimited, and when non core threads are idle, they will be recycled immediately. Thread pools such as ScheduledThreadPool are mainly used to execute scheduled tasks and repetitive tasks with fixed cycles. The implementation of the newScheduledThreadPool method is as follows:

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

It is created through the newsinglethreadexector method of Executors. There is only one core thread in this kind of thread pool, which ensures that all tasks are executed sequentially in the same thread. The significance of singlethreadexecution is to unify all external tasks into one thread, which makes it unnecessary to deal with thread synchronization between these tasks. The implementation of the newSingleThreadExecutor method is as follows:

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

The above describes in detail the four common thread pools in Android. In addition to the four types of thread pools provided by the above system, thread pools can also be flexibly configured according to actual needs. The following code demonstrates the typical usage of the four preset thread pools:

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);//Good at performing scheduled tasks and repetitive tasks with fixed cycles
scheduledThreadPool.schedule(command, 2000, TimeUnit.MILLISECONDS);//Execute command after 2000ms
scheduledThreadPool.scheduleAtFixedRate(command, 10, 100, TimeUnit.MILLI_SECONDS);//After a delay of 10ms, execute the command every 1000ms

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

Topics: Java Android Android Studio