Using a Thread Pool in Android
In my last post Using HanderThread in Android, I showed how to offload short blocking tasks to a worker thread. While HandlerThread is good for tasks running in a sequential manner, there are cases where background tasks do not have dependencies on each other. To get these tasks done as quickly as possible, you might want to exploit the powerful multi-core processor on the mobile device and run the tasks concurrently on more than one worker thread.
A thread pool is a good fit for this scenario. Thread pool is a single FIFO task queue with a group of worker threads. The producers (E.g. the UI thread) sends tasks to the task queue. Whenever any worker threads in the thread pool become available, they remove the tasks from the front of the queue and start running them.
Comparing with starting a random number of individual worker threads, using a thread pool prevent the overhead of killing and recreating threads every time a worker thread is needed. It also gives you fine control over the number of threads and their lifecycle. E.g. ThreadPoolExecutor allows you to specify how many core threads, how many max threads the pool should create and the keep alive time for the idle threads.
Android supports Java’s Executor framework which offers the following classes for using a thread pool.
- Executor: an interface which has a execute method. It is designed to decouple task submission from running.
- Callable: An Interface similar to runnable but allow a result to be returned.
- ExecutorService: an interface which extends Executor interface. It is used to manage threads in the threads pool.
- ThreadPoolExecutor: a class that implements ExecutorService which gives fine control on the thread pool (Eg, core pool size, max pool size, keep alive time, etc.)
- ScheduledThreadPoolExecutor: a class that extends ThreadPoolExecutor. It can schedule tasks after a given delay or periodically.
- Executors: a class that offers factory and utility methods for the aforementioned classes.
- ExecutorCompletionService: a class that arranges submitted task to be placed on a queue for accessing results.
Basic Thread Pool
The simplest way of creating a thread pool is to use one of the factory methods from Executors class.
newFixedThreadPool creates a thread pool with a a fixed number of thread in the pool specified by the user. The user can call `setCorePoolSized(int)` later to resize the thread pool.
newCachedThreadPool creates a new thread when there is a task in the queue. When there is no tasks in the queue for 60 seconds, the idle threads will be terminated.
newSingleThreadExecutor creates a thread pool with only one thread.
To add a task to the thread pool, call one of the following methods.
The second method returns a future object. It can be used to retrieve the result from the callable by calling future.get() or cancel the task by calling future.cancel(boolean mayInterruptIfRunning).
Advanced Thread Pool
If you want to have finer control over the thread pool, ThreadPoolExecutor class can be used. In the following example, I first find the available processors of the phone. The thread pool is configured to have core size as the NUMBER_OF_CORES, the maximum core size as the NUMBER_OF_CORES x 2, idle threads’ keep-alive time as 1 second, task queue as a LinkedBlockingQueue object and a custom thread factory.
To stop the tasks in the task queue from execution, we just need to clear the task queue. To allow the running threads to be stopped, store all future objects in a list and call cancel on every object which is not done.
Handle Activity Lifecycle
One thing the thread pool framework does not handle is the Android activity lifecycle. If you want your thread pool to survive the activity lifecycle and reconnect to your activity after it is re-created (E.g. after an orientation change), it needs to be created and maintained outside the activity.
In my example, I made a static singleton class called CustomThreadPoolManager. It has a private constructor. It creates an instance of itself and return that single instance in the static getInstance method. It also holds a weak reference to the Activity. The reference is later used to communicate with the UI thread (see the next section).
In the Activity, get the thread pool singleton instance by calling the getInstance static method. Set the activity to the CustomThreadPoolManager. As CustomThreadPoolManager keeps the reference to the Activity as a weak reference, you don’t need to worry about leaking the Activity.
Communicate with UI Thread
When each task finishes, you may need to send some data back to the UI thread. A safe way of doing this is to send a message to the handler of the UI thread. First, extend Handler class and define what the UI thread should do when a message is received.
In the CustomThreadPoolManager, use the Activity’s weak reference to send the message to the UI thread.
In the CustomCallable, as it has reference to the CustomThreadPoolManager, it can send the message by calling CustomThreadPoolManager’s sendMessageToUiThread method.
The full source code for the example used in this post is available on Github.