Application performance can make or break a user’s experience. Applications that provide a consistent experience and perform well are more likely to benefit user productivity. The Jakarta Concurrency specification targets application performance and usability by providing a standard API for developing concurrent application processes without compromising container integrity. It is not recommended to utilize traditional Java SE threads or timers within a Jakarta EE container because use of threads or timers may cause reliability issues and unexpected results. Jakarta Concurrency provides an API that does not intrude on container integrity, but rather, uses services that are managed by the container and matches the functionality provided by the Java SE Concurrency Utilities.
Developers who are familiar with the Java SE Concurrency utilities API
will be able to leverage their existing knowledge to use this API, as
there are many similarities. Containers extend the java.util.concurrent
API by providing managed versions of java.util.concurrent.ExecutorService
interfaces. All Jakarta EE compliant containers include default executor
services, context services, and thread factories for performing
particular tasks such as spawning asynchronous tasks and scheduling tasks
to be executed at a specified time. However, it is also possible to
develop custom services, if needed, and register them with a container.
Jakarta Concurrency is part of all Jakarta EE compatible containers. However, to explicitly add the Jakarta Concurrency API to a maven application, add the following dependency:
<dependency>
<groupId>jakarta.enterprise.concurrent</groupId>
<artifactId>jakarta.enterprise.concurrent-api</artifactId>
<version>3.0.2</version>
</dependency>
Jakarta EE compliant containers must contain a ManagedExecutorService
,
which extends the Java SE ExecutorService
. Tasks can be passed to a
ManagedExecutorService, and those tasks can then be performed within a
container managed thread and passed back to the caller once complete.
This process can occur asynchronously, allowing the application to
continue performing other tasks while the ManagedExecutorService
processes the task which had been handed off. The ManagedExecutorService
returns a Future
object to the caller, which will return a boolean
indicating when the task has been completed. The caller can poll this
Future object and then perform some action using the resulting object
once the task has completed.
A task is developed by creating a class that must implement
java.util.concurrent.Callable
or java.lang.Runnable
, or any of the
functional interfaces that can be supplied to a
java.util.concurrent.CompletionStage
. A Callable
task class can
optionally return a result to the caller. When tasks are submitted to a
managed executor or managed CompletionStage, they become contextual,
behaving in the same manner as they had within the original container.
This means that contextual information and state can be passed to the
ManagedExecutorService
within the task and utilized while processing.
For example, a session may contain the username of an authenticated user,
and that information can be passed to the ManagedExecutorService
within
the task. A task class that implements Runnable
may be structured as
follows:
public class MyTask implements Runnable {
// Inject resources, if needed
public void run() {
// execute logic
}
}
For more fine grained control, a task class can optionally implement the
jakarta.enterprise.concurrent.ManagedTask
interface, which enables the
ability to perform actions upon lifecycle events. This is achieved by
registering a jakarta.enterprise.concurrent.ManagedTaskListener
instance with the task. A use case for implementing ManagedTask
may be
to initiate a process when a particular lifecycle event occurs.
To pass a task to a ManagedExecutorService
, the
ManagedExecutorService
can be obtained for use within a resource
through use of the Java Naming and Directory Interface (JNDI).
Alternatively, the ManagedExecutorService
can be configured via XML
within the web.xml of the application. After the resource has been
obtained, then the task can be submitted to it by calling upon any one of
the submit()
, execute()
, invokeAll()
, invokeAny()
, runAsync()
,
or supplyAsync()
methods. The following code demonstrates how to obtain
the ManagedExecutorService
resource and submit a task to it.
@Resource(name = "concurrent/_defaultManagedExecutorService")
ManagedExecutorService managedExecutorService;
// Pass task class to the ManagedExecutorService
Future futureObject = managedExecutorService.submit(myTask);
If there are a number of tasks which need to be executed in parallel, it
is possible to do so by passing an array of Runnable
or Callable
tasks to
the ManagedExecutorService
invokeAll()
method. The following code
demonstrates how to construct an array of builder tasks and submit them
to the ManagedExecutorService
.
ArrayList<Callable<TaskType>> builderTasks = new ArrayList<Callable<<TaskType>>();
builderTasks.add(new TaskType(1));
builderTasks.add(new TaskType(2));
List<Future<TaskType>> results = managedExecutorService.invokeAll(builderTasks);
In circumstances where transactions are required,
jakarta.transaction.UserTransaction
can be used to utilize transactions
within task classes. In order to do so, obtain the SessionContext
resource, and then utilize it to obtain a UserTransaction
.
The ManagedScheduledExecutorService
is a container resource that
extends the java.util.concurrent.ScheduledExecutorService
and
jakarta.enterprise.concurrent.ManagedExecutorService
interfaces to
provide the same managed functionality as a ManagedExecutorService, with
the additional ability to delay and schedule tasks to run at a specific
time. Task classes, which implement either java.lang.Runnable
or
java.util.concurrent.Callable
, can be passed to the
ManagedScheduledExecutorService
and executed at a delayed or scheduled
interval. To pass a task to the ManagedScheduledExecutorService
, the
service must be obtained for use into a resource via JNDI.
Alternatively, the ManagedScheduledExecutorService
can be configured via
XML within the web.xml of the application. Once obtained, task(s) can be
submitted by calling upon any one of the submit()
, execute()
,
invokeAll()
, invokeAny()
, runAsync()
, supplyAsync()
, schedule()
,
scheduleAtFixedRate()
, or scheduleWithFixedDelay()
methods. The
following code demonstrates how to obtain a
ManagedScheduledExecutorService
resource and schedule a task.
@Resource(name="concurrent/_defaultManagedScheduledExecutorService")
ManagedScheduledExecutorService managedScheduledExecutorService;
// Pass task class to the ManagedExecutorService
Future futureHandle = managedExecutorService.scheduleAtFixedRate(myTask, 10, TimeUnit.MINUTES);
A ManagedThreadFactory
can be used to create new thread instances within
a Jakarta EE container without directly utilizing java.util.Thread
.
These managed thread instances can be registered using JNDI.
Alternatively, ManagedThreadFactory
can be configured via XML within the
web.xml descriptor. Managed threads execute much like a standard thread,
however, they execute in the same context as the container. To utilize a
ManagedThreadFactory
, inject an instance into a resource and then call
upon the newThread()
, passing the Runnable
or Callable
task class.
Lastly, invoke the start()
method on the new thread to begin. Using the
example task class from the previous section, the following code
demonstrates how to create a new thread and pass the task to the thread:
@Resource(name="concurrent/MyManagedThreadFactory")
ManagedThreadFactory threadFactory;
. . .
// Create task and pass to thread
MyTask task = new MyTask();
Thread taskThread = threadFactory.newThread(task);
taskThread.start();
. . .
// Destroy the thread if it is no longer needed
taskThread.interrupt();
The ContextService
can be used to create contextual objects for Jakarta
EE without the use of a ManagedExecutorService
. The ContextService
utilizes dynamic proxy capabilities which are found within the
java.lang.reflect
package or creates non-dynamic proxy instances in order
to associate the context of the container with an object instance(s).
Since the contextual object proxy will be run as an extension of the
container that creates it, the proxy may interact with the container
resource. The contextual object proxy can also be stored and run at a
later time, if needed. Such solutions are useful for developing
applications that require context throughout a process, such as a
workflow. The ContextService
can be registered using JNDI.
Alternatively, a ContextService
can be configured via XML.
Jakarta EE 10 introduced asynchronous methods to the Jakarta Concurrency
specification. Asynchronous methods enable developers to annotate
methods contained within CDI beans in order to have those methods invoked
asynchronously. The CDI bean must not be a Jakarta Enterprise Bean, and
the asynchronous method cannot be annotated with any library asynchronous
annotations. Asynchronous methods should contain a return type of
java.util.concurrent.CompletableFuture
,
java.util.concurrent.CompletionStage
, or void
. Behind the scenes, the
application container will execute the method within a
java.util.concurrent.CompletableFuture
or
jakarta.enterprise.concurrent.ManagedExecutorService
. As such, the
container context is propagated to asynchronous methods. The following
code demonstrates an example of developing an asynchronous method:
@Asynchronous(executor="java:module/env/concurrent/myExecutor")
public CompletableFuture<String> myMethod() {
// Processing
return Asynchronous.Result.complete(string);
}
Jakarta Concurrency contains a ThreadContextProvider
SPI to provide a
standard means for third party thread context types to be provided
alongside the Jakarta EE container’s built-in thread context types. This
means that there is a standard way for third party developers to create
types that can be utilized within a compliant Jakarta EE container
without breaking compliance.
Jakarta Concurrency provides a standard means for developing concurrent solutions within a Jakarta EE container without compromising the integrity of the container. Compliant Jakarta EE containers have executor services, context services, and managed thread factories configured, which can accept tasks and execute them asynchronously or at scheduled intervals. Through the use of these services, container context is propagated to tasks and container resources are available for use by tasks.