In order to perform an asynchronous task, a Thread is required. If you have to perform a large number of tasks then you might need to initialize multiple threads. As there is a limit to the number of threads which can be executed in parallel, you will need to create and destroy these threads again and again. This is suboptimal because of following reasons:
- There will be a lot of overhead of creating and destroying multiple thread objects.
- Also, management of the number of threads which can be initialized or the number of tasks which can be executed in parallel becomes very difficult.
A Thread Pool overcomes both of these issues. It has a pre-defined pool of pre-initialized long running threads. Whenever a task needs to be executed, it is assigned to one of these threads. Once the task is done, that thread again becomes available for the next task.
Thus, it provides improved performance by reducing the overhead of thread creation and destruction. Also, it provides a means of bounding and managing resources. For obvious reasons, this performance gain is huge when a large number of short tasks need to be executed.
Initialization
When a thread pool is initialized, its core pool size, maximum pool size, keep alive time and work queue are defined. After initialization, based on the requirement, either
- one thread can be initialized which can idly wait for a task to be submitted or,
- all the threads equal to core pool size can be initialized which can all idly wait for tasks to be submitted or,
- threads can be initialized on demand whenever a new task is submitted
After initialization, a thread pool is ready to accept requests for task execution.
Source code for basic and advanced implementations for a Thread Pool based on below mentioned concepts is available here.
Important Components
A Thread Pool Executor has three major components:
- Work Queue (e.g. BlockingQueue)
- Workers (e.g. Set)
- Thread Factory
Work Queue
Maintains a list of all the tasks which need to be performed. A work queue can be bounded (e.g. ArrayBlockingQueue) or unbounded (e.g. LinkedBlockingQueue).
Workers
As the name implies, a Worker (implements Runnable) is the one who picks the task from Work Queue and executes it. Once done, it waits for more tasks to be available in the Work Queue. The moment a task is available in the queue, it fetches it and starts executing it. This cycle/loop keeps on going until an exceptional termination condition has been met or no more tasks are available based on one of the two conditions (in the given order):
- till keep alive time if either idle thread timeout has been defined or worker count is more than core pool size
- until a new task is available in the queue
The maximum number of workers allowed can be Integer.MAX_VALUE.
Note: ThreadPoolExecutor in Java has limited this to 29 bits, (2^29)–1 as it uses the last 3 bits of the same integer field to keep track of its own state.
Thread Factory
A Thread Factory is used to create new threads which are required to run the workers. Each Worker runs in a different thread. Basically, when a thread’s start method is called, it calls run method overridden by the Worker which takes the Worker in a task execution loop as mentioned above.
Important Attributes
A Thread Pool Executor has some important properties which affect the Worker management:
- core pool size, the minimum number of workers that need to be maintained. If the worker count ever goes below this, a new worker is created when a new task is submitted to the executor instead of storing it in the queue. If workers count is more this number then idle workers are killed.
- maximum pool size, the maximum number of workers which can be running at a particular moment. If worker count reaches this limit, no new workers are created in the executor and newly submitted tasks are stored in the queue. So, whenever any worker is free, it picks up an available task from the queue.
- keep alive time, the time after which an idle worker would be killed.
-
allow core thread timeout, a boolean which tells the system if idle core workers should be killed.
Shutdown
When asked to shut down, the thread pool executor will change its state to SHUTDOWN and will interrupt all the workers. If asked for immediate shutdown then it will interrupt all the workers which have started and drain the task queue else it will interrupt only idle workers and wait for running workers to complete tasks from the task queue. Once the shutdown is triggered, no new tasks are accepted.
Source code for basic and advanced implementations for a Thread Pool based on above mentioned concepts is available here.