This article focuses on Using Kotlin to discuss thread pool usage in Android development.

When we want to use threads, we can create child threads and start them

Thread { Log.d("rfDev"."rustfisher said: hello") }.start()
Copy the code

If you have a large number of asynchronous tasks, you don’t want to create child threads every time. Is there a unified way to manage child threads?

In this case, we can consider thread pools. Thread pools solve two problems: when a large number of asynchronous tasks need to be executed, they reduce the invocation overhead of each asynchronous task and improve performance. It also has the ability to restrict and manage child threads. Each ThreadPoolExecutor maintains some statistics, such as the number of tasks executed.

Consider using thread pools when you have a large number of asynchronous tasks.

Preset thread pool

Refer to Android API 29 for the code

ThreadPoolExecutor provides many parameters for developers to control. Thread pool designers recommend the following factory methods, five of which are available on Android

  • newCachedThreadPool()Unlimited number of thread pools, automatically reclaim threads
  • newFixedThreadPool(int nThreads)A fixed number of thread pools
  • newSingleThreadExecutor()Single child thread
  • newScheduledThreadPool(int corePoolSize)Can perform delayed or periodic tasks
  • newWorkStealingPool()Work stealing thread pools

In fact, a number of options pop up when we type Executors. New in Android Studio.

Executors. New Smart tips

Cacheable thread pools

With Executors. NewCachedThreadPool for an object can be cached thread pool, then let it to perform a task.

val tp: ExecutorService = Executors.newCachedThreadPool()
tp.submit { Log.d(TAG, "Rustfisher: Cached thread pool for task 3")}Copy the code

The cacheable thread pool creates new child threads as needed. Existing threads are reused when they become available. This mechanism is suitable for performing multiple short-term asynchronous tasks. The tasks are small, but the number is large.

Calling the execute method first attempts to reuse existing available threads. If there are no threads, a new thread is created and added to the pool. Threads that have not been used for more than 60 seconds are stopped and removed. Therefore, even if the thread pool is not used for a long time, there is no significant overhead.

Fixed-length thread pool

Use the newFixedThreadPool(int nThreads) example

val fixedTp: ExecutorService = Executors.newFixedThreadPool(4)
fixedTp.submit { Log.d(TAG, "Rustfisher fixed length thread pool to execute tasks")}Copy the code

The static method passes an int nThreads, which indicates the maximum number of threads. If all threads are currently busy, a new task is added. The task then waits in the queue until there are available threads to process the task.

If a thread encounters an error and stops, a new thread is created to fill in the slot to execute the task.

Threads in the pool live until the thread pool stops (ExecutorService#shutdown).

Single thread pool

val singleTp: ExecutorService = Executors.newSingleThreadExecutor()
singleTp.submit { Log.d(TAG, "Single thread pool performing tasks")}Copy the code

It only has 1 child thread. Task queues do not limit the number of tasks. If the thread encounters a problem and stops, a new thread will be created to handle the next task.

It ensures that tasks are processed sequentially and that only one task can be processed at a time.

After a single thread pool is created, the number of threads cannot be changed dynamically. Unlike newFixedThreadPool(1), a fixed-length thread pool can change the number of threads.

Schedule the task thread pool

val scheduleTp: ScheduledExecutorService = Executors.newScheduledThreadPool(3)
Copy the code

Scheduled task thread pools are capable of executing deferred and periodic tasks.

Delayed tasks

Need to set delay and time units

scheduleTp.schedule({ Log.d(TAG, "Scheduled Task 1 Runnable")},300, TimeUnit.MILLISECONDS)
scheduleTp.schedule(Callable { Log.d(TAG, Scheduled Task 2 Callable)},400, TimeUnit.MILLISECONDS)
Copy the code

Cycle task

It mainly involves two methods scheduleAtFixedRate and scheduleWithFixedDelay.

Assuming that the task time is less than the period time, the task is performed according to the given period time. The two methods appear to be consistent.

Assuming that the task takes longer than the cycle time, the two approaches are somewhat different

  • scheduleAtFixedRateAfter the execution of the previous task exceeds the cycle time, the next task is immediately executed.
  • scheduleWithFixedDelayAfter the execution of the previous task, it waits for the cycle time before executing the next task.

Work stealing thread pools

The Android SDK is greater than or equal to 24, and there is a new thread pool, tentatively called the “job-stealing thread pool”, or “flexible scheduling thread pool”.

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
    Executors.newWorkStealingPool()
}
Copy the code

A thread pool maintains enough threads to support a given level of parallelism, possibly with multiple queues to reduce contention. Parallelism corresponds to the maximum number of active threads, or the maximum number of threads that can process tasks.

The actual number of threads can increase or decrease dynamically. The work stealing thread pool does not guarantee that tasks will be processed in the order they are submitted.

Perform a task

You can pass Runnable and Callable to perform a task.

Use the Callable example

tp.submit(Callable { "OK" })
Copy the code

A call to a task with no return value

Callable and Runnable are both used for tasks with no return value.

val tp: ExecutorService = Executors.newCachedThreadPool()
tp.submit { Log.d(TAG, "Rustfisher: Cached thread pool submit Runnable") }
tp.execute { Log.d(TAG, "Rustfisher: Cached thread pool execute Runnable") }
tp.submit(Callable { Log.d(TAG, "Rustfisher: Cached thread pool submit Callable") })

tp.shutdown() // Finally, stop the thread pool when it is finished
Copy the code

Calls to tasks with return values

Tasks with return values require the Callable interface.

submit

A Future object is returned when the Submit method is called. The return value is obtained through the Future’s get() method. Note here that get() blocks, and when you’re done, you get the return value.

val tp: ExecutorService = Executors.newCachedThreadPool()
val future = tp.submit(Callable {
    return@Callable "Return value of callable"
})
Log.d(TAG, "Future get before isDone:${future.isDone}, isCancelled: ${future.isCancelled}")
val res = future.get()
Log.d(TAG, "Future get after isDone:${future.isDone}, isCancelled: ${future.isCancelled}")
Log.d(TAG, "future get: $res")
Copy the code

Run the log

Future Get isDone: false, isCancelled: false Future Get isDone: true, isCancelled: false Future Get: Callable return valueCopy the code

invokeAll

For tasks in a list, use invokeAll(Collection
> Tasks), returns a list of futures. For comparison, add a delay to one of the tasks.

InvokeAll sample

    val tp: ExecutorService = Executors.newFixedThreadPool(5)
    val callList = arrayListOf<Callable<String>>(
            Callable {
                Log.d(TAG, "task1 ${Thread.currentThread()}")
                return@Callable "rust"
            },
            Callable {
                Log.d(TAG, "task2 ${Thread.currentThread()}")
                Thread.sleep(1500) // Add delay
                return@Callable "fisher"
            },
            Callable {
                Log.d(TAG, "task3 ${Thread.currentThread()}")
                return@Callable "Tasks on the list"
            },
    )
    Log.d(TAG, "InvokeAll is ready to submit the task")
    val futureList = tp.invokeAll(callList)
    Log.d(TAG, "InvokeAll has submitted the task")
    futureList.forEach { f ->
        Log.d(TAG, "Task list execution result${f.get()}") // this will block get in the UI thread
    }
Copy the code

Run log, you can see that after submitting the task, after a delay, get the result of the run. Note the time before and after invokeAll. InvokeAll blocks the current thread. When used, care must be taken not to call from the UI thread.

The 2021-09-11 14:40:07. 062, 16914-16914 / com. Rustfisher. Tutorial2020 D/rfDevTp: InvokeAll ready to submit the task 14:40:07 2021-09-11. 063. 16914-19230 / com rustfisher. Tutorial2020 D/rfDevTp: Task1 Thread [- Thread pool - 4-1, 5, the main] 14:40:07. 2021-09-11, 063, 16914-19231 / com. Rustfisher. Tutorial2020 D/rfDevTp: Task2 Thread [- Thread pool - 4-2, 5, the main] 14:40:07. 2021-09-11, 063, 16914-19232 / com. Rustfisher. Tutorial2020 D/rfDevTp: Task3 Thread [- Thread pool - 4-3, 5, the main] 14:40:08. 2021-09-11, 563, 16914-16914 / com. Rustfisher. Tutorial2020 D/rfDevTp: InvokeAll submitted task 14:40:08. 2021-09-11, 563, 16914-16914 / com. Rustfisher. Tutorial2020 D/rfDevTp: Task list execution result rust 14:40:08. 2021-09-11, 563, 16914-16914 / com. Rustfisher. Tutorial2020 D/rfDevTp: Task list execution results fisher 14:40:08 2021-09-11. 563. 16914-16914 / com rustfisher. Tutorial2020 D/rfDevTp: result list in the task listCopy the code

Three tasks were submitted and executed in three different child threads.

invokeAny

invokeAny(Collection
> Tasks) is also a collection that receives Callable. It then returns the value of the first completed task, and other unfinished tasks are cancelled normally without exception.

InvokeAny sample

    val tp: ExecutorService = Executors.newCachedThreadPool()
    val callList = arrayListOf<Callable<String>>(
            Callable {
                Thread.sleep(1000) // Design delay
                return@Callable "rust"
            },
            Callable {
                Thread.sleep(400)
                return@Callable "fisher"
            },
            Callable {
                Thread.sleep(2000)
                return@Callable "Tasks on the list"
            },
    )
    Log.d(TAG, "InvokeAny submit tasks")
    val res = tp.invokeAny(callList)
    Log.d(TAG, The results of the implementation are as follows:$res")
Copy the code
The 2021-09-11 14:04:55. 253, 14066-14066 / com. Rustfisher. Tutorial2020 D/rfDevTp: InvokeAny submitting 14:04:55 2021-09-11. 654. 14066-14066 / com rustfisher. Tutorial2020 D/rfDevTp: fisher execution resultsCopy the code

Looking at the log, you can see that the last task is “Fisher”.

Stopping the thread pool

When finished, remember to terminate the thread pool

/*ExecutorService*/ shutdown()
shutdownNow()
Copy the code

Shutdown () creates a stop command after the submitted task, and no new tasks are accepted. Calling this method will not take effect if the thread pool has stopped.

The shutdownNow() method tries to stop all executing tasks and stop waiting tasks. And returns a List of tasks waiting to be executed List

.

Reference: an.rustfisher.com/android/con…