User Tools

Site Tools


java:threadpoolexecutor

ThreadPoolExecutor

This class is used to execute tasks in Java. Generally, users will not directly create a ThreadPoolExecutor, but instead use one of the the static factory methods in `Executors` to create one. For example:

  1. class Runner {
  2.  
  3. private final ExecutorService executor = Executors.newCachedThreadPool();
  4.  
  5. public void run(Runnable r) {
  6. executor.execute(r);
  7. }
  8. }

Implementation

ThreadPoolExecutor is implemented by two sets of queues:

  1. The task queue. This is a |BlockingQueue that stores pending tasks. When calls to `submit()` or `executor()` are made, the tasks are added to this queue.
  2. The thread queue. This queue is less visible. ThreadPoolExecutor maintains several threads, which each try to pull tasks off the task queue. In an executor with a fixed number of threads (e.g. newFixedThreadPool()), each thread tries to pull an item off the queue and execute it.

If there are no items to take off the task queue, the threads wait to try and take the next item that gets added. In this way, they form a natural queue, each waiting in line to get the next task to execute.

When submitting a work item to a ThreadPoolExecutor, the caller tries to add the item to the task queue. What happens if there is no room on the task queue? ThreadPoolExecutor tries to spawn a new thread to accept the new task. The behavior here is different depending on the core pool size.

  • In a cached ThreadPoolExecutor, the core pool size is 0 and the task queue is permanently empty. When a caller tries to submit a task, there is no space in the queue. This is due to the task queue being a `SynchronousQueue`, which requires a consuming thread to be listening on it. Because the caller cannot add the item to the queue, and because the core pool size is less than the max pool size, ThreadPoolExecutor spawns a new thread to handle the additional task.
  • In a fixed ThreadPoolExecutor, the core pool size is N where N was set at instantiation. When a caller tries to submit a task, the task is added to a (usually unbounded) task queue, and then consumed by one of the N threads later.

As we can see, the core pool size has an intricate interplay with the task queue. By constraining the task queue, we can make ThreadPoolExecutor spawn more threads, up to the maximum. By unbounding the task queue, we can limit the number of threads handling tasks. By default, a cached threadpool is both an unlimited number of thread, and a limited task queue. A fixed threadpool is an unlimitted number of tasks, and a limited thread pool. The constructor overloads of ThreadPoolExecutor let us customize this even more, but these are the most common cases. The behavior of the pool is a consequence of the task queue and thread spawning rules.

ScheduledThreadPoolExecutor

This class is similar to ThreadPoolExecutor, but doesn't allow you to change the task queue. The task queue is a priority queue, sorted on the task deadline.

Living without a Queue

A consequence of not being able to bound the queue is that the thread count is more important:

  • You cannot have a cached ScheduledThreadPoolExecutor. ThreadPoolExecutor will not create a thread if submission to the task queue succeeds. Since submission always succeeds, no threads will be created.
  • As mentioned in the javadocs, a core pool size of 0 is practically useless, since no threads will be alive to process the task.
  • If all the threads in the ScheduledThreadPoolExecutor are busy (e.g. blocking), no new tasks can be executed. This is important for periodically running tasks, such as those submitted with scheduleAtFixedRate() and scheduleWithFixedDelay(). These tasks will not be run in a timely manner.
java/threadpoolexecutor.txt · Last modified: by carl