The Spring TaskExecutor Abstraction

Executors are the JDK name for the concept of thread pools. The “executor” naming is due to the fact that there is no guarantee that the underlying implementation is actually a pool. An executor may be single-threaded or even synchronous. Spring’s abstraction hides implementation details between the Java SE and Java EE environments.

Spring’s TaskExecutor interface is identical to the java.util.concurrent.Executor interface. In fact, originally, its primary reason for existence was to abstract away the need for Java 5 when using thread pools. The interface has a single method (execute(Runnable task)) that accepts a task for execution based on the semantics and configuration of the thread pool.

The TaskExecutor was originally created to give other Spring components an abstraction for thread pooling where needed. Components such as the ApplicationEventMulticaster, JMS’s AbstractMessageListenerContainer, and Quartz integration all use the TaskExecutor abstraction to pool threads. However, if your beans need thread pooling behavior, you can also use this abstraction for your own needs.

TaskExecutor Types

Spring includes a number of pre-built implementations of TaskExecutor. In all likelihood, you should never need to implement your own. The variants that Spring provides are as follows:

  • SyncTaskExecutor: This implementation does not execute invocations asynchronously. Instead, each invocation takes place in the calling thread. It is primarily used in situations where multi-threading is not necessary, such as in simple test cases.

  • SimpleAsyncTaskExecutor: This implementation does not reuse any threads. Rather, it starts up a new thread for each invocation. However, it does support a concurrency limit that blocks any invocations that are over the limit until a slot has been freed up. If you are looking for true pooling, see ThreadPoolTaskExecutor, later in this list.

  • ConcurrentTaskExecutor: This implementation is an adapter for a java.util.concurrent.Executor instance. There is an alternative (ThreadPoolTaskExecutor) that exposes the Executor configuration parameters as bean properties. There is rarely a need to use ConcurrentTaskExecutor directly. However, if the ThreadPoolTaskExecutor is not flexible enough for your needs, ConcurrentTaskExecutor is an alternative.

  • ThreadPoolTaskExecutor: This implementation is most commonly used. It exposes bean properties for configuring a java.util.concurrent.ThreadPoolExecutor and wraps it in a TaskExecutor. If you need to adapt to a different kind of java.util.concurrent.Executor, we recommend that you use a ConcurrentTaskExecutor instead.

  • WorkManagerTaskExecutor: This implementation uses a CommonJ WorkManager as its backing service provider and is the central convenience class for setting up CommonJ-based thread pool integration on WebLogic or WebSphere within a Spring application context.

  • DefaultManagedTaskExecutor: This implementation uses a JNDI-obtained ManagedExecutorService in a JSR-236 compatible runtime environment (such as a Java EE 7+ application server), replacing a CommonJ WorkManager for that purpose.

Using a TaskExecutor

Spring’s TaskExecutor implementations are used as simple JavaBeans. In the following example, we define a bean that uses the ThreadPoolTaskExecutor to asynchronously print out a set of messages:

import org.springframework.core.task.TaskExecutor;

public class TaskExecutorExample {

	private class MessagePrinterTask implements Runnable {

		private String message;

		public MessagePrinterTask(String message) {
			this.message = message;
		}

		public void run() {
			System.out.println(message);
		}
	}

	private TaskExecutor taskExecutor;

	public TaskExecutorExample(TaskExecutor taskExecutor) {
		this.taskExecutor = taskExecutor;
	}

	public void printMessages() {
		for(int i = 0; i < 25; i++) {
			taskExecutor.execute(new MessagePrinterTask("Message" + i));
		}
	}
}

As you can see, rather than retrieving a thread from the pool and executing it yourself, you add your Runnable to the queue. Then the TaskExecutor uses its internal rules to decide when the task gets executed.

To configure the rules that the TaskExecutor uses, we expose simple bean properties:

<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
	<property name="corePoolSize" value="5"/>
	<property name="maxPoolSize" value="10"/>
	<property name="queueCapacity" value="25"/>
</bean>

<bean id="taskExecutorExample" class="TaskExecutorExample">
	<constructor-arg ref="taskExecutor"/>
</bean>