Navigating the Waves of Concurrency: Exploring Jakarta Concurrency

Gautham Krishnan
4 min readMar 18, 2024

--

“In a concurrent world, imperative is the wrong default!”

In the vast ocean of software development, concurrency stands as a formidable wave to be mastered. With the ever-increasing demand for high-performance, scalable applications, understanding and effectively implementing concurrency becomes crucial. Among the many tools and frameworks available, Jakarta Concurrency shines as a beacon, offering developers a robust set of tools to navigate the complexities of concurrent programming in Java. In this blog post, we’ll explore Jakarta Concurrency through practical code examples, demonstrating its key features and best practices.

Understanding Concurrency

“I’m not saying writing concurrent code is hard, but even the ‘Hello, World!’ program deadlocks sometimes.”

Concurrency, simply put, is the ability of a system to execute multiple tasks concurrently. In the realm of software development, this often means having multiple threads of execution running simultaneously. While this can lead to significant performance improvements and better resource utilisation, it also introduces challenges such as race conditions, deadlocks, and thread synchronisation issues.

Enter Jakarta Concurrency

Jakarta Concurrency, formerly known as Java EE Concurrency Utilities, is a powerful framework that provides high-level concurrency abstractions and utilities for Java applications. Part of the larger Jakarta EE ecosystem, Jakarta Concurrency offers developers a rich set of tools to tackle complex concurrency scenarios with ease.

Key Features and Components

Before diving into code examples, let’s first ensure we have the necessary dependencies set up. Jakarta Concurrency is part of the Jakarta EE ecosystem, so make sure you have a compatible Jakarta EE runtime or application server installed. Additionally, ensure that you have the Jakarta Concurrency API included in your project dependencies.

<dependency>
<groupId>jakarta.enterprise.concurrent</groupId>
<artifactId>jakarta.enterprise.concurrent-api</artifactId>
<version>{version}</version>
<scope>provided</scope>
</dependency>

Managed Executors

One of the core components of Jakarta Concurrency is its managed executor service. This allows developers to offload tasks to a pool of worker threads, providing automatic management of thread lifecycle and resource allocation. Managed executors simplify the process of parallelising tasks and help optimize resource utilisation.

Let’s see how we can use a managed executor to execute a task asynchronously.

..

@Resource
private ManagedExecutorService managedExecutorService;

@GET
@Path("managedExecuters")
@Produces(MediaType.TEXT_PLAIN)
public String getmanagedExecuters() throws InterruptedException, ExecutionException {
Future future1 = managedExecutorService.submit(() -> {
System.out.println("Job 1 running ...");
// This takes some while
System.out.println("Job 1 finished ...");
});
Future future2 = managedExecutorService.submit(() -> {
System.out.println("Job 2 running ...");
// This takes some while
System.out.println("Job 2 finished ...");
});
future1.get();
future2.get();
System.out.println("Jobs completed");
return "Jobs completed";
}
..

The output of the above program in console:

[INFO] Job 1 running ...
[INFO] Job 2 running ...
[INFO] Job 1 finished ...
[INFO] Job 2 finished ...
[INFO] Jobs completed

Context Propagation

Another essential feature of Jakarta Concurrency is its support for context propagation. This allows contextual information, such as security context or transaction context, to be automatically propagated across asynchronous execution boundaries. This ensures consistency and integrity, especially in distributed systems where context management can be challenging.

..

@Resource
ContextService contextService;

@Resource
ManagedExecutorService managedExecutorService;

@GET
@Path("contextPropagation")
@Produces(MediaType.TEXT_PLAIN)
public String getContextPropagation() throws InterruptedException, ExecutionException {

//Execution 1
managedExecutorService.execute(() -> {
try {
System.out.println(new InitialContext().lookup("java:comp/env/replySuccess"));
} catch (NamingException namingException) {
namingException.printStackTrace();
}
});
ExecutorService executor = Executors.newFixedThreadPool(2);

// Executer without context
//Execution 2
executor.submit(() -> {
try {
System.out.println(new InitialContext().lookup("java:comp/env/replySuccess"));
} catch (NamingException namingException) {
System.err.println("NamingException on ExecuterService as no context available.");
}
});

//Execution 3
executor.submit(contextService.contextualRunnable(() -> {
try {
System.out.println(new InitialContext().lookup("java:comp/env/replySuccess"));
} catch (NamingException namingException) {
namingException.printStackTrace();
}
}));

return "Application context propogated with java.util.concurrent.ExecutorService using jakarta.enterprise.concurrent.ContextService.";
}

..

In this example:

  • ManagedExecuterService in Execution 1 is running with the Application Context and will be able to access JNDI lookup.
  • ExecuterService in Execution 2 will not have Application Context and will not be able to access JNDI lookup.
  • For getting the context, ExecuterService in Execution 3 is paired with the ContextService.

The output of the above program in console:

INFO] Hello from java:comp/env! The document was inserted successfully!
[INFO] [err] NamingException on ExecuterService as no context available.
[INFO] Hello from java:comp/env! The document was inserted successfully!

Managed Threads

With Jakarta Concurrency, developers can leverage managed threads to execute tasks in a controlled and managed environment. This helps mitigate common pitfalls associated with manual thread management, such as resource leaks and inefficient resource utilisation.

Let’s dive into an example demonstrating the usage of managed threads:

..

@Resource
private ManagedThreadFactory managedThreadFactory;

@GET
@Path("managedThreads")
@Produces(MediaType.TEXT_PLAIN)
public String getManagedThreads() throws InterruptedException, ExecutionException {
// Create and start a managed thread
Thread thread = managedThreadFactory.newThread(() -> {
System.out.println("Managed thread is executing");
});
thread.start();
return "Managed thread created";
}

..

Embracing Best Practices

While Jakarta Concurrency offers powerful tools for concurrent programming, it’s essential to follow best practices to ensure robust and reliable applications:

  1. Use Managed Executors: Whenever possible, leverage managed executors for task execution to benefit from automatic thread lifecycle management and resource optimization.
  2. Contextual Awareness: Ensure proper propagation of contextual information across asynchronous boundaries to maintain consistency and integrity in your applications.
  3. Avoid Shared Mutable State: Minimize the use of shared mutable state between concurrent tasks to reduce the risk of race conditions and synchronization issues.
  4. Error Handling: Implement robust error handling and recovery mechanisms to gracefully handle exceptions and failures in concurrent execution paths.

Conclusion

Concurrency is a powerful tool in the arsenal of modern software development, but it comes with its own set of challenges. Jakarta Concurrency provides developers with a comprehensive set of tools and utilities to tackle these challenges effectively. By leveraging managed executors, context propagation, and asynchronous event handling, developers can design high-performance, scalable applications that harness the full potential of concurrency. However, it’s crucial to follow best practices and guidelines to ensure the reliability and stability of concurrent systems. With Jakarta Concurrency, developers can navigate the waves of concurrency with confidence, unlocking new possibilities in Java application development.

“Hello, world? Hold on, I’ll put you on hold, spawn a few more threads, and get back to you.”

Want to try it out? jakarta-concurrency-demo using Open Liberty.

Happy coding :)

--

--