Introduction:

On Android, threads are divided into main threads, which handle things related to the interface, and sub-threads, which typically perform time-consuming operations. It is not wise to create destroy threads frequently, and using thread pools is the right thing to do. Thread pools cache a certain number of threads, so you can avoid the overhead of frequently creating and destroying threads. AsyncTask is the underlying thread pool, IntentService/HandlerThread is the underlying thread.

The main content

  • Main thread and child thread
  • Thread patterns in Android
  • Android thread pool

The specific content

In Android, threads take many forms:

  • AsyncTask encapsulates a thread pool and a Handler.
  • A HandlerThread is a thread that has a message loop and can use a handler internally
  • IntentService is a Service that uses HandlerThread to execute tasks. After a task is executed, the IntentService automatically exits. Because it is a Service, it is not easily killed by the system.

In operating system, thread is the smallest unit of operating system scheduling, while thread is a limited system resource, its creation and destruction will have corresponding overhead. At the same time, when the system has a large number of threads, the system will schedule each thread through the way of time slice rotation, so it is impossible to achieve absolute concurrency, unless the number of threads is less than or equal to the number of CPU cores.

Main thread and child thread

The main thread mainly processes the interface interaction logic. Because users can interact with the interface at any time, the main thread needs to have a high response speed at any time, so it cannot execute time-consuming tasks.

Android3.0, network access will fail and throw NetworkOnMainThreadException this exception, this is done to avoid the main thread by time-consuming operation due to obstruction which ANR phenomenon now.

Thread patterns in Android

AsyncTask

AsyncTask is a lightweight asynchronous task class. It can perform background tasks in the thread pool, and then pass the progress and result of the execution 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. However, AsyncTask is not suitable for background tasks that are time-consuming, and Thread pools are recommended for such tasks.

AsyncTask is an abstract generic class. The meaning of these three generics:

  1. Params: Parameter type
  2. Progress: indicates the Progress type of background tasks
  3. Result: Type of Result returned by background task

If you do not need to pass specific arguments, these three generic arguments can be replaced by Void.

Four methods:

  • onPreExecute()

This method is called before an asynchronous task is executed on the main thread, and is generally used to do some preparatory work.

  • doInBackground()

Executed in a thread pool, this method is used to execute an asynchronous task with the parameter params representing the input parameter of the asynchronous task. In this method, the progress of the task can be updated with the publishProgress() method, which calls the onProgressUpdate() method and returns the calculation to onPostExecute().

  • onProgressUpdate()

Executed on the main thread, this method is called after the asynchronous task has executed, where the result argument is the return value of the background task, that is, doInBackground().

  • onPostExecute()

Executed on the main thread, this method is called after the asynchronous task is executed, where the result argument is the return value of the background task, the return value of doInBackground.

In addition to the above four methods, there is also onCancelled(), which is executed on the main thread. OnCancelled () is called when the asynchronous task is cancelled, but onPostExecute() is not called.

AsyncTask has some limitations when it is used:

  • The AsyncTask class must be loaded in the main thread, which means that the first access to AsyncTask must occur in the main thread. This issue is not absolute, as in Android 4.1 and above it has been automatically done by the system. In the Android 5.0 source code, you can see that ActivityThread#main() calls AsyncTask#init().
  • AsyncTask objects must be created in the main thread.
  • The execute method must be called in the UI thread.
  • Do not call onPreExecute(), onPostExecute(), doInBackground, and onProgressUpdate() directly in the program.
  • An AsyncTask object can be executed only once, that is, the execute() method can be called only once, otherwise a runtime exception will be reported.
  • Prior to Android 1.6, AsyncTask performed tasks sequentially; In Android 1.6 AsyncTask started using thread pools to process parallel tasks. However, since Android 3.0, in order to avoid concurrency errors caused by AsyncTask, AsyncTask uses a single thread to execute tasks sequentially. However, after 3.0, it is still possible to execute tasks in parallel through the AsyncTask#executeOnExecutor() method.
How AsyncTask works

AsyncTask has two thread pools (SerialExecutor and THREAD_POOL_EXECUTOR) and one Handler(InternalHandler). The thread pool SerialExecutor is used for sorting tasks. The thread pool THREAD_POOL_EXECUTOR is used for the actual execution of the task, and InternalHandler is used to switch the execution environment from thread to main thread, which is still essentially the call process of the thread.

Queuing AsyncTask: The AsyncTask#Params parameter is first wrapped as a FutureTask object, which is a concurrent class that acts as a Runnable. The FutureTask is then handed over to the SerialExecutor#execute() method. This method first inserts the FutureTask object into the task queue mTasks, and if no AsyncTask task is active at that time, the SerialExecutor#scheduleNext() method is called to execute the next AsyncTask task. At the same time, after an AsyncTask is completed, the AsyncTask will continue to execute other tasks until all tasks are completed. It can be seen from this that AsyncTask is executed in serial by default.

HandlerThread

Looper.prepare() is used to create a message queue, and looper.loop () is used to start a message loop. This allows the creation of handlers in the HandlerThread for practical use.

The implementation of HandlerThread has significant differences from ordinary threads. A common Thread is used to perform a time-consuming task in the run method. The HandlerThread creates a message queue internally, and the external world needs to use Handler messages to tell the HandlerThread to perform a specific task. HandlerThread is a useful class, and one example of its use on Android is IntentService.

Since HandlerThread#run() is a wireless loop method, it is best to terminate the thread by using the quit() or quitSafely() method when it is clear that HandlerThread is no longer needed.

IntentService

IntentSercie is a special kind of Service, and inherits the Service is an abstract class, after the completion of the task will automatically stop, far above the ordinary priority thread, suitable to perform some high priority background tasks; IntentService encapsulates HandlerThread and Handler

  1. OnCreate automatically creates a HandlerThread
  2. It uses its Looper to construct a Handler object, mServiceHandler, so that messages sent through the mServiceHandler are executed on the HandlerThread.
  3. IntentServiced onHandlerIntent method is an abstract method that requires subclass implementation. After executing onHandlerIntent, stopSelt(Int startId) is serviced. If multiple background tasks are performed, The service will not be stopped until the last stopSelf(int startId) is executed.

Android thread pool

Advantages:

  • Reusing threads from a thread pool reduces the performance overhead associated with thread creation and destruction.
  • Control the maximum number of concurrent threads in the thread pool to avoid blocking caused by a large number of threads competing for system resources.
  • Provides periodic execution and interval cyclic execution functions.

Android in the thread pool is derived from the concept of the Executor in Java, Executor is an interface, the realization of the real thread pool for ThreadPoolExecutor. Most of the Android thread pool is providing the factory method to create Executor. ThreadPoolExecutor provides a set of parameters to configure a thread pool. Different thread pools can be created using different parameters. From the features of the function can be divided into four categories.

ThreadPoolExecutor

ThreadPoolExecutor is a true implementation of a thread pool. Its constructor provides a set of parameters to configure the thread pool, which directly affect the functionality of the thread pool.

public ThreadPoolExecutor(int corePoolSize,
                 int maximumPoolSize,
                 long keepAliveTime,
                 TimeUnit unit,
                 BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
    Executors.defaultThreadFactory(), defaultHandler);
}
Copy the code
  • CorePoolSize: The number of core threads in the thread pool. By default, core threads are always alive in the thread pool, even if they are idle. If the ThreadPoolExecutor#allowCoreThreadTimeOut attribute is set to true, idle core threads will have a policy of timeout while waiting for new tasks to arrive, which is determined by the keepAliveTime attribute. The core thread terminates when the wait time exceeds the keepAliveTime value.
  • MaximumPoolSize: The maximum number of threads that the thread pool can hold, after which the number of active threads will be blocked.
  • KeepAliveTime: The timeout period during which non-core threads are idle before they are reclaimed. AllowCoreThreadTimeOut also applies to core threads when the allowCoreThreadTimeOut attribute is true.
  • Unit: Specifies the time unit for keepAliveTime. This is an enumeration. We use timeutil.milliseconds, timeutil.seconds and timeutil.minutes.
  • WorkQueue: A queue of tasks in a thread pool in which Runnable objects submitted through the execute method of the thread pool are stored.
  • ThreadFactory: a threadFactory that provides the ability to create new threads for a thread pool. ThreadFactory is an interface.

ThreadPoolExecutor performs tasks according to the following rules:

  1. If the number of threads in the thread pool does not reach the number of core threads, a core thread is directly started to perform the task.
  2. If the number of threads in the thread pool has reached or exceeded the number of core threads, the task is inserted into the task queue and queued for execution.
  3. If the task cannot be inserted into the task queue in Step 2, this is usually because the task queue is full, and if the number of threads does not reach the maximum specified in the thread pool, a non-core thread is immediately started to execute the task.
  4. If the number of threads in Step 3 has reached its maximum, the task will be rejected and ThreadPoolExecutor will call the RejectedExecution method to notify the caller.

AsyncTask THREAD_POOL_EXECUTOR thread pool configuration:

  • The number of core threads equals the number of CPU cores +1.
  • The maximum number of threads in the thread pool is 2 times the number of CPU cores +1.
  • Core threads have no timeout mechanism, and the idle timeout time of non-core threads is 1 second.
  • The task queue capacity is 128.
Classification of thread pools
  • FixedThreadPool

Create from the Executor#newFixedThreadPool() method. It is a thread pool with a fixed number of threads. When threads are idle, they are not reclaimed unless the pool is closed. When all threads are active, new tasks wait until a thread is free. Because FixedThreadPool has only core threads and these core threads are not recycled, this means that it can respond to requests from the outside world more quickly.

  • CachedThreadPool

Create from the Executor#newCachedThreadPool() method. It is a thread pool with an indefinite number of threads. It has only non-core threads and its maximum number of threads is integer.max_value. This can be considered as arbitrarily large. When all threads in the thread pool are active, the pool creates new threads to handle new tasks, otherwise it uses idle threads to handle new tasks. All idle threads in the thread pool have a timeout mechanism. The timeout duration is 60 seconds. If the timeout duration exceeds this, idle threads will be reclaimed. Unlike FixedThreadPool, CachedThreadPool’s task queue is essentially an empty collection, which causes any task to be executed immediately because SynchronousQueue cannot insert tasks in this scenario. SynchronousQueue is a very special queue that, in many cases, can be understood simply as a queue that cannot store elements. Rarely used in practice. These threads are better suited for performing a large number of small tasks.

  • ScheduledThreadPool

From the Executor#newScheduledThreadPool() method. The number of core threads is fixed, while the number of non-core threads is unlimited and is immediately recycled when idle. This type of thread pool is used to perform scheduled tasks and repetitive tasks with fixed cycles.

  • SingleThreadExecutor

Create from the Executor#newSingleThreadPool() method. This type of thread pool has only one core thread inside it, which ensures that all tasks are executed sequentially in the same thread. The point of this thread pool is to unify all external tasks into one thread, so that there is no need to deal with thread synchronization between these tasks.