Asynchronous programming tools in Android development is the most recommended is Kotlin coroutine, before the introduction of Kotlin coroutine mechanism, in addition to responsive extension (RxJava) concurrent asynchronous programming tools, Java API threads and thread pool is the most important asynchronous programming means. However, for the Implementation of The Kotlin coroutine on the Android platform, the thread pool is still used as the carrier of task execution, so the simple understanding of the Kotlin coroutine on the Android platform is a high encapsulation of the thread pool.

Executors.newFixedThreadPool(10).asCoroutineDispatcher()
Dispatchers.IO.asExecutor()
Copy the code

So let’s first look at how Java thread pools work and then dive into how Kotlin coroutines are implemented.

From the Thread to the Executor

Threads are created through the Thread class and pooled for reuse. Thread pools offer two obvious advantages:

  1. Reduces the overhead of repeatedly creating threads
  2. Decouple tasks from thread management

The Executor interface is the embodiment of the second point. The execute method is used to execute the task, regardless of the carrier of the task execution and whether the thread is created. The ThreadPoolExecutor implementation class is the thread pool implementation of this task executor.

ThreadPoolExecutor adds tasks with thread reuse

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }/ / 1
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null.false);
        }/ / 2
        else if(! addWorker(command,false))
            reject(command);/ / 3
    }
Copy the code

Look at the execute method to get a clear idea of how it works:

  1. When the number of threads is less than corePoolSize, the thread is created and the task is executed.
  2. If the task is not added through Step 1, the task is added to the workQueue. (The main logic is in the condition judgment of if, while the logic in if deals with the rollback or replenishment of the create thread on some exception)
  3. If the task is not enqueued, a thread (maximum maximumPoolSize) is created and executed. If the task fails, a reject policy is executed.

Boolean addWorker(Runnable firstTask, Boolean core) is the method that creates the thread. The second parameter in this method is bounded by corePoolSize or maximumPoolSize. The logic for creating the rest of the threads in the method does not go into detail. However, we should pay attention to the wrapper class Worker of the thread. The addWorker method calls the start method of the encapsulated thread in the Worker and executes the run method of the Worker. We simplify the runWorker inside the run method as follows:

    void runWorker(Worker w) {
        Runnable task = w.firstTask;
        w.firstTask = null;
        while(task ! =null|| (task = getTask()) ! =null) { task.run(); }}Copy the code

It can be found that after the initial task is executed, the getTask method is constantly used to obtain the task execution to realize the reuse of the thread, rather than destroying the thread after only one task is executed.

Also check out the simplified getTask method as follows:

    private Runnable getTask(a) {
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        try {
            Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
            if(r ! =null)
                return r;
        } catch (InterruptedException retry) { }
    }
Copy the code

AllowCoreThreadTimeOut and whether the number of threads in the workQueue is greater than corePoolSize. Poll Does it block the take method to destroy the thread if the task list is empty?

Back to the ThreadPoolExecutor constructor:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
Copy the code

We can clearly understand what each parameter means and how it affects the reuse of threads in the thread pool.

Related reading: Thread pools in Kotlin coroutines