Do you know about Android threads and thread pools?

Posted by lm_a_dope on Wed, 09 Oct 2019 03:06:16 +0200

In Android, threads are divided into main threads and sub-threads. The main interface is used to interact with users and perform UI-related operations, while sub-threads are responsible for time-consuming operations. If time-consuming operations are performed in the main thread, the program will not be able to respond in time. Therefore, time-consuming operations must be carried out in sub-threads.

1. Main threads and sub-threads

The main thread refers to all the threads used by the process. In Android, the thread that interacts with the user is the main thread. Therefore, in Android development, it is necessary to put time-consuming operations, network request access operations, database read operations in sub-threads, so as to avoid the main thread being occupied for a long time to reduce the user experience. The system requires that network access must be done in sub-threads, otherwise the NetworkOnMainThreadException exception will be thrown.

2. Thread Form

Thread patterns in Android include traditional Thread, AsyncTask, Handler Thread and IntentService.

2.1,AsyncTask

AsyncTask encapsulates Thread and Handler and must be invoked in the main thread. It can execute tasks in the sub-threads, then pass the results of execution to the main thread and update the UI. However, AsyncTask is not suitable for performing particularly time-consuming tasks.

2.1.1. Parameters:

AsyncTask is a generic class that provides three generic parameters, Params, Progress and Result.

  • Params represents the type of parameter
  • Progress represents the type of execution progress of background tasks
  • Result represents the type of result returned by background tasks

AsyncTask's statement:

public abstract class Asynctask<Params,Progress,Result>

2.1.2. Methods:

AsyncTask provides some core approaches:

  • onPreExecute() calls in the main thread the preparation operation used for asynchronous tasks.
  • doInBackground(Params... Params is automatically invoked when subthreaded tasks are performed after onPreExecute(), and Params represents the input parameters of asynchronous tasks. In the method, the completion progress of the task can be updated by publishProgress, and the result will be returned to the onPostExecute() method after the call is completed.
  • onProgressUpdate(Params... Params is used in the main thread to show the progress of the task and is called in the publishProgress() method.
  • onProgressExecute(Result result) in the main thread, the user obtains the result of the return after the task ends, that is, the return value of doInBackground.
  • onCancelled() executes in the main thread. When the asynchronous task is cancelled, onCancelled method is executed instead of onProgressExecute method.

2.1.3. Call:

The steps of using AsyncTask are as follows: 1. Creating AsyncTask subclasses and implementing its core method 2. Creating instance objects of subclasses 3. Calling execute to perform asynchronous threading tasks

2.1.4. Limitations of AsyncTask:

1. It must be loaded in the main thread, that is, the first access to AsyncTask must be in the main thread, and the object must be created in the main thread. Excute must be called in the UI thread. 3. The onPreExecute(),onPostExecute(),doInBackground and onProgressUpdate methods can no longer be called directly in the program. 4. An AsyncTask method can only be used once

2.1.5. The working principle of AsyncTask:

  • execute method
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;
}

In the execute method, the current state of AsyncTask is detected. If the current state is RUNNING or FINISHED, the system throws an exception, and when it is idle, it is RUNNING. When set to RUNNING, the onPreExecute method is called and the parameter Params is passed to mParams of mWorker. Then exec.execute is called and passed into mFuture, where exec is the sDefaultExecutor passed in.

  • sDefaultExecutor sDefaultExecutor is a serial thread pool in which all syncTasks in a process are queued. In the executeOnExecute method, onPreExecute method is the first one to execute.
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

public static final Executor SERIAL_EXECUTOR = new 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);
        }
    }
}

mTasks represent the task cache queue of Serial Executor, which is a serial thread pool. Then a task is added to the task cache queue with offer. The run method of r is called as the incoming mFuture, while the call method of MWorker is called inside the run method of mFuture, and then the doInBackground method is called to start the background task. After execution, the scheduleNext method is called to perform the next task. In addition, mActive represents the object of AsyncTask, and if the MActive is null, the scheduleNext method is also executed. In the scheduleNext method, if the cache queue is not empty, the task is taken out of the queue to execute. In summary, Serial Executor's job is to add tasks to the cache queue, while the task is performed by THREAD_POOL_EXECUTOR.

  • THREAD_POOL_EXECUTOR
public static final Executor THREAD_POOL_EXECUTOR
        = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

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

That is, the number of CPUs of core PoolSize is increased by one, the number of CPUs of MAXUMUM PoolSize is doubled and doubled, and the task cache queue is listed as Linked Blocking Queue.

  • postResult method
private Result postResult(Result result) {
    @SuppressWarnings("unchecked")
    Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
            new AsyncTaskResult<Result>(this, result));
    message.sendToTarget();
    return result;
}

First, the getHandler method is used to get the sHandler contained in AsyncTask, and then MESSAGE_POST_RESULT message is sent.

  • sHandler
private static final InternalHandler sHandler = new InternalHandler();

private static class InternalHandler extends Handler {
    public InternalHandler() {
        super(Looper.getMainLooper());
    }

    @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // There is only one result
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
} 

SHandler is a static Andler object. In order for sHandler to be able to switch its execution environment from background to main thread, the main thread's Loper should be used to create sHandler in the main thread. When sHandler receives MESSAGE_POST_RESULT, it calls the finish method. The source code is as follows:

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

Call isCancelled() to determine whether the task is cancelled, onCancelled(result) is called if cancelled, or onPostExecute(result) is called. At the same time, the mStatus is set to FINISHED to indicate that the object has been executed.

2.2,HandlerThread

2.2.1. Introduction and Implementation

Handler Thread inherits Thread and can use Handler, which is simple to implement and saves system resource overhead. The realization is as follows:

HandlerThread thread = new HandlerThread("MyHandlerThread");
thread.start();
mHandler = new Handler(thread.getLooper());
mHandler.post(new Runnable(){...});

2.2.2, Source Code and Analysis

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }

    protected void onLooperPrepared() {
    }

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

    public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit();
            return true;
        }
        return false;
    }

}

It creates a message queue, which needs to be notified to perform a task through Handler's message. Because Handler Thread's run method is an infinite loop method, it can terminate thread execution through quit method.

2.3,IntentService

IntentService is a special service that inherits Service, because IntentService is an abstract class, so you must create a subclass of IntentService to use it. At the same time, IntentService is a service, so it has a higher priority in execution. IntentService encapsulates Handler Thread and Handler.

2.3.1. Use

1. Define the IntentService subclass, pass in the thread name, and override the onHandlerIntent() method. 2. Register in Manifest.xml 3. Open services in Activity

2.3.2. Source code analysis

  • OnCreate calls the corresponding onCreate method when IntentThread is first started. Specifically as follows:
@Override
public void onCreate() {
    super.onCreate();
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
    thread.start();

    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper); 
}

First, the new thread is instantiated, then the ooper of the worker thread is obtained, and a handler object mServiceHandler is constructed. Messages sent through mServiceHandler are executed in Handler Thread.

  • onStartCommand

Each time the IntentService is started, the onStartCommand method is called to process the Intent for each background task.

public int onStartCommand(Intent intent, int flags, int startId) {

    onStart(intent, startId);
    return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

public void onStart(Intent intent, int startId) {

    Message msg = mServiceHandler.obtainMessage();
    msg.arg1 = startId;
    msg.obj = intent;
    mServiceHandler.sendMessage(msg);
}

You can see that the onStart method is called in onStartCommand. In the onStart method, you get a reference to the ServiceHandler message, wrap the message in msg, and then send the message. This message will be processed in Handler Thread.

  • ServiceHandler Method
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's onHandleIntent method is an abstract method, which needs to be implemented. Its function is to distinguish specific tasks from Intent and execute them. After calling the task, stopSelf will stop the service directly. When there are multiple tasks, stopSelf will stop the service after the last task is executed.

3. Thread pool

In practice, thread pools tend to be more scalable. Compared with threads, the performance overhead of thread pools is smaller, which can avoid blocking due to a large number of threads grabbing resources and is easier to manage.

The interface of thread pool in java is ExecutorService, and the default implementation is ThreadPoolExecutor.

3.1,ThreadPoolExecutor

ThreadPool Executor is an implementation of thread pool, which is constructed as follows

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

  • corePoolSize

Number of core threads, core threads will survive in the thread pool. If allowCoreThreadTimeOut is set to true, the core thread will also have timepout, which will be killed after the timeout.

  • maximumPoolSize

Maximum number of threads.

  • keepAliveTime

Non-core threads have a long timeout limit. Non-core threads will be withdrawn after the setup time is exceeded, and core threads will be withdrawn after allowCoreThreadTime Out is set to true.

  • Unit time parameter unit
  • workQueue

Task queue in which unnable objects submitted through the execute method of thread pool are stored

  • threadFactory

Thread factory to create threads for thread pools

ThreadPool Executor meets certain rules when performing threaded tasks

  • Start with core threads, enabling new threads when the core threads are insufficient
  • When the task book is larger than the number of core threads, it is inserted into the waiting queue
  • If the waiting queue cannot be inserted and the number of threads does not reach the maximum number, a new thread will be opened
  • If the waiting queue cannot be inserted and new threads cannot be created, the request will be rejected.

3.2. Classification of thread pools

Thread pool is divided into four different functions: Fixed ThreadPool, Cached ThreadPool, Scheduled ThreadPool and Singke ThreadExecutor.

  • FixedThreadPool

By using the new Fixed ThreadPool method of execute, a fixed size thread pool is created with each submission until the maximum number of threads in the pool is reached. If a thread exception ends, a new thread is generated to replenish it. Be able to respond to external requests more quickly. The concrete realization is as follows:

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

  • CachedThreadPool is a thread pool that can create threads as needed. For many short-term asynchronous tasks, it will effectively improve performance. Calling execute () will reuse the constructed threads. If no threads are available, it will create a new thread and join the thread pool, and remove more than 60 seconds of unused threads. Suitable for less time-consuming tasks. The concrete realization is as follows:
public static ExecutorService newCachedThreadPool(){
    reutrn new ThreadPoolExecutor(0,Interger.MAX_VALUE,
                            60L,TimeUnit.SECONDS,
                            new SynchronousQueue<Runnable>());
}

  • Scheduled ThreadPool has a limited number of core threads and a wireless number of non-core threads, creating an infinite thread pool that supports the need to perform tasks on a regular and periodic basis. The concrete realization is as follows:
public static ScheduledThreadPool newScheduledThreadPool(int corePoolSize){
    reutrn new ScheduledThreadPool(corePoolSize);
}

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

  • SingkeThreadExecutor creates a single thread pool, that is, there is only one thread in the pool, and all tasks are executed serially. If the thread in the pool ends abnormally, a new thread will replace it. This ensures that tasks are performed in the order of submission. The concrete realization is as follows:
public static ExecutorService newScheduledThreadExecutor(){
    reutrn new FinalizeableDelegatedExecutorService
            (new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECINDS,new LinkedBlockingQueue<Runnable>()));
}

Last

If you see it here and think it's a good article, give it a compliment! _____________ Welcome to comment and discuss! If you think it's worth improving, please leave me a message. We will make serious inquiries, correct shortcomings, and regularly share technical dry goods free of charge. Like the little partner can pay attention to oh. Thank you!

Topics: Mobile Android network Database xml