November 25, 2017

Java EE 7 Concurrency Utilities (JSR 236) and Example

Introduction

The Concurrency Utilities (JSR 236) is completely new in Java EE 7 and is also new in the Java EE mindset. In previous Java EE version the idea of creating new threads was forbidden and the motivation behind it was that thread was error prune and the standard components in Java EE should be enough. In Java EE 7 that has changed and the developer is free again to create threads.

But the above reason holds still true, that threads can help your application to scale better and increase performance, but it can also introduce:

  • Deadlocks
  • Thread starvation
  • Concurrent accessing of shared resources

Reference Oracle The Java EE 7 Tutorial Chapter 56.1 Concurrency Basics

And if you are new to Threads you should also read the Java Standard Edition Tutorial about Threads Oracle Java Tutorial Lesson: Concurrency. Which also points out the following pitfalls

  • Thread Interference describes how errors are introduced when multiple threads access shared data.
  • Memory Consistency Errors describes errors that result from inconsistent views of shared memory.
  • Synchronized Methods describes a simple idiom that can effectively prevent thread interference and memory consistency errors.
  • Implicit Locks and Synchronization describes a more general synchronization idiom, and describes how synchronization is based on implicit locks.
  • Atomic Access talks about the general idea of operations that can't be interfered with by other threads.

Reference Oracle Java Tutorial Synchronization

And if you are dead serious about threads you should read the book Java Concurrency in Practice by Brian Goetz.

Out first Thread

So now when we knew that we must pay extra attention when writing our threads, lets create a new thread. This can be done from either: java.lang.Runnable, java.lang.Thread or java.util.concurrent.Callable. The first two are there for backward compatibility and when writing new code always use java.util.concurrent.Callable.

package se.magnuskkarlsson.example.javaee7.task.control;

import java.util.concurrent.Callable;
import java.util.logging.Logger;

public class TaskCallable implements Callable<Integer> {

    private final Logger log = Logger.getLogger(TaskCallable.class.getName());

    private final String id;

    public TaskCallable(String id) {
        this.id = id;
    }
    
    @Override
    public Integer call() throws Exception {
        log.info("[" + id + "] Starting a long running task");
        for (int i = 0; i < 3; ++i) {
            log.info("[" + id + "] Analysing task...");
            // We simulate now a long running task
            Thread.sleep(3000);
        }
        log.info("[" + id + "] Finished a long running task");
        // return some dummy data
        return (int) (Math.random() * 100);
    }

}

Then we want to test. Lets test it with a simple JAX-RS class.

package se.magnuskkarlsson.example.javaee7.task.boundary;

import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.ejb.LocalBean;
import javax.ejb.Startup;
import javax.enterprise.concurrent.ManagedExecutorService;
import javax.inject.Singleton;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import se.magnuskkarlsson.example.javaee7.task.control.TaskCallable;

@Startup
// we need to have only one and same instance, so we can clean up ManagedExecutorService
@Singleton
// Designates that a session bean exposes a no-interface view.
// This annotation is required if a session bean exposes any other client views 
// (local, remote, no-interface, 2.x Remote Home, 2.x Local Home, **Web Service**)
@LocalBean
@Path("/task")
public class TaskREST {

    @Resource
    ManagedExecutorService managedExecutorService;
    
    @PreDestroy
    public void destroy() {
        managedExecutorService.shutdownNow();
    }
    
    @POST
    public void create(@QueryParam("id") String id) {
        managedExecutorService.submit(new TaskCallable(id));
    }
    
}

And finally test with simple CURL commands.

$ curl -X POST http://localhost:8080/example-javaee7/rest/task?id=foo
$ curl -X POST http://localhost:8080/example-javaee7/rest/task?id=bar
$ curl -X POST http://localhost:8080/example-javaee7/rest/task?id=code
$ curl -X POST http://localhost:8080/example-javaee7/rest/task?id=nisse

Now we can see in the log that task are started, ran and finished.

21:45:12,129 INFO  [se.magnuskkarlsson.example.javaee7.task.control.TaskCallable] (EE-ManagedExecutorService-default-Thread-1) [foo] Starting a long running task
21:45:12,130 INFO  [se.magnuskkarlsson.example.javaee7.task.control.TaskCallable] (EE-ManagedExecutorService-default-Thread-1) [foo] Analysing task...
21:45:15,130 INFO  [se.magnuskkarlsson.example.javaee7.task.control.TaskCallable] (EE-ManagedExecutorService-default-Thread-1) [foo] Analysing task...
21:45:18,131 INFO  [se.magnuskkarlsson.example.javaee7.task.control.TaskCallable] (EE-ManagedExecutorService-default-Thread-1) [foo] Analysing task...
21:45:21,132 INFO  [se.magnuskkarlsson.example.javaee7.task.control.TaskCallable] (EE-ManagedExecutorService-default-Thread-1) [foo] Finished a long running task
21:45:33,249 INFO  [se.magnuskkarlsson.example.javaee7.task.control.TaskCallable] (EE-ManagedExecutorService-default-Thread-2) [bar] Starting a long running task
21:45:33,250 INFO  [se.magnuskkarlsson.example.javaee7.task.control.TaskCallable] (EE-ManagedExecutorService-default-Thread-2) [bar] Analysing task...
21:45:35,288 INFO  [se.magnuskkarlsson.example.javaee7.task.control.TaskCallable] (EE-ManagedExecutorService-default-Thread-3) [code] Starting a long running task
21:45:35,289 INFO  [se.magnuskkarlsson.example.javaee7.task.control.TaskCallable] (EE-ManagedExecutorService-default-Thread-3) [code] Analysing task...
21:45:36,250 INFO  [se.magnuskkarlsson.example.javaee7.task.control.TaskCallable] (EE-ManagedExecutorService-default-Thread-2) [bar] Analysing task...
21:45:38,209 INFO  [se.magnuskkarlsson.example.javaee7.task.control.TaskCallable] (EE-ManagedExecutorService-default-Thread-4) [nisse] Starting a long running task
21:45:38,210 INFO  [se.magnuskkarlsson.example.javaee7.task.control.TaskCallable] (EE-ManagedExecutorService-default-Thread-4) [nisse] Analysing task...
21:45:38,290 INFO  [se.magnuskkarlsson.example.javaee7.task.control.TaskCallable] (EE-ManagedExecutorService-default-Thread-3) [code] Analysing task...
21:45:39,251 INFO  [se.magnuskkarlsson.example.javaee7.task.control.TaskCallable] (EE-ManagedExecutorService-default-Thread-2) [bar] Analysing task...
21:45:41,210 INFO  [se.magnuskkarlsson.example.javaee7.task.control.TaskCallable] (EE-ManagedExecutorService-default-Thread-4) [nisse] Analysing task...
21:45:41,290 INFO  [se.magnuskkarlsson.example.javaee7.task.control.TaskCallable] (EE-ManagedExecutorService-default-Thread-3) [code] Analysing task...
21:45:42,251 INFO  [se.magnuskkarlsson.example.javaee7.task.control.TaskCallable] (EE-ManagedExecutorService-default-Thread-2) [bar] Finished a long running task
21:45:44,210 INFO  [se.magnuskkarlsson.example.javaee7.task.control.TaskCallable] (EE-ManagedExecutorService-default-Thread-4) [nisse] Analysing task...
21:45:44,290 INFO  [se.magnuskkarlsson.example.javaee7.task.control.TaskCallable] (EE-ManagedExecutorService-default-Thread-3) [code] Finished a long running task
21:45:47,211 INFO  [se.magnuskkarlsson.example.javaee7.task.control.TaskCallable] (EE-ManagedExecutorService-default-Thread-4) [nisse] Finished a long running task

Summary

The main class in Concurrency Utilities (JSR 236) are:

  • javax.enterprise.concurrent.ManagedExecutorService - which you can <T> Future<T> submit(Callable<T> task) task. java.util.concurrent.Future can also be preemptive shutdown by calling java.util.concurrent.Future#cancel(true).
  • The other that is just like above, but for the difference it can schedule threads for later, is javax.enterprise.concurrent.ManagedScheduledExecutorService.

No comments: