DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Ulyp: Recording Java Execution Flow for Faster Debugging
  • Mastering Concurrency: An In-Depth Guide to Java's ExecutorService
  • Building a Skill-Based Agentic Reviewer with Claude Code: A Practical Guide Using Skills.MD, MCP Servers, Tools, and Tasks
  • Jakarta EE Glossary: The Terms Every Java Engineer Should Actually Understand

Trending

  • Docker Hardened Images Are Free Now — Here's What You Still Need to Build
  • DevOps and Platform Engineering Readiness Checklist: Everything Needed for a Scalable, Secure, High-Velocity Delivery Platform
  • Architecting an Embedded Efficiency Layer: A Platform Deep Dive into Day-Two Operational Tuning
  • The Agentic Agile Office: Streamlining Enterprise Agile With Autonomous AI Agents
  1. DZone
  2. Coding
  3. Java
  4. Navigating the Waves of Concurrency: Exploring Jakarta Concurrency

Navigating the Waves of Concurrency: Exploring Jakarta Concurrency

This tutorial explores Jakarta Concurrency through practical code examples, demonstrating its key features and best practices.

By 
Gautham Krishnan user avatar
Gautham Krishnan
·
Jul. 22, 24 · Tutorial
Likes (11)
Comment
Save
Tweet
Share
3.4K Views

Join the DZone community and get the full member experience.

Join For Free

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 offers developers a robust set of tools to navigate the complexities of concurrent programming in Java.

In a concurrent world, imperative is the wrong default!

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 utilization, it also introduces challenges such as race conditions, deadlocks, and thread synchronization issues.

Jakarta Concurrency

Jakarta Concurrency, formerly known as Java EE Concurrency Utilities, is a 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.

XML
 
<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 parallelizing tasks and help optimize resource utilization.

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

Java
 
..

    @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 the console:

Shell
 
[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.

Java
 
..

    @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.
  • To get the context, ExecuterService in Execution 3 is paired with the ContextService.

The output of the above program in the console:

Shell
 
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 utilization.

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

Java
 
..

    @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? Go to the jakarta-concurrency-demo using Open Liberty.

Happy coding :)

Java EE Tool Execution (computing) Java (programming language) Task (computing)

Published at DZone with permission of Gautham Krishnan. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Ulyp: Recording Java Execution Flow for Faster Debugging
  • Mastering Concurrency: An In-Depth Guide to Java's ExecutorService
  • Building a Skill-Based Agentic Reviewer with Claude Code: A Practical Guide Using Skills.MD, MCP Servers, Tools, and Tasks
  • Jakarta EE Glossary: The Terms Every Java Engineer Should Actually Understand

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook