Prior to Java EE 7 one could make use of the concurrency utilities present in java.util.concurrent
package or the java.lang.Thread
or java.lang.Runnable
. But it was not considered a best practice, not a standard and not safe for the application. This was because the Java EE web and EJB containers instantiate objects using container-managed thread pools and creating a new (non-managed) Thread object, the container could not guarantee that other Java EE platform services (for example, transactions and security) would be part of this Thread.
Due to this major drawback independent, unmanaged threads were not created in a Java EE application. With Java EE 7 a new API has been added to make the features of java.util.concurrent package available to the Java EE application in a safe and standard way. In this post I am going to look at one such API called ManagedExecutorService. This is a managed version of ExecutorService API present in java.util.concurrent
package.
What is a ManagedExecutorService?
From the JavaDocs:
A ManagedExecutorService extends the Java™ SE ExecutorService to provide methods for submitting tasks for execution in a Java™ EE environment. Implementations of the ManagedExecutorService are provided by a Java™ EE Product Provider. Application Component Providers use the Java Naming and Directory Interface™ (JNDI) to look-up instances of one or more ManagedExecutorService objects using resource environment references. ManagedExecutorService instances can also be injected into application components through the use of the Resource annotation.
Following are the important observations from the description above:
- Extends the Java SE
ExecutorService
- Provides methods for submitting tasks for execution in a Java EE environment
- Implementations are provided by a Java EE producy provider
- Use JNDI to look-up instances of one ore more
ManagedExecutorService
objects ManagedExecutorService
can also be injected into the application components through the use of the resource annotation
In the Point-2 above the tasks are written by either implementing the java.util.concurrent.Callable
or the java.lang.Runnable
interfaces. The difference between java.util.concurrent.Callable
and java.lang.Runnable
are:
Callable
contains acall()
method which returns a value where asRunnable
containsrun()
method which doesnt return any value.Callable
can throw checked exceptions where asRunnable
cannot.
Using the ManagedExecutorService
I am going to divide this section into 4 parts namely:
- Obtaining instance of default
ManagedExecutorService
using JNDI lookup - Submitting tasks using the
submit()
method. - Submitting tasks using the
invokeAll()
method. - Submitting tasks using the
invokeAny()
method.
Obtaining instance of default ManagedExecutorService
As mentioned before the default instance of ManagedExecutorService can be obtained by using a JNDI lookup. This lookup can be done by executing the method or via the annotation. The JDNI lookup for default is: java:comp/DefaultManagedExecutorService
. One can provide their own JNDI lookups which has to be configured in the web.xml
. Lets look at the below code which obtains the default ManagedExecutorService instance using both the approaches:
//Using method invocation //Create a InitContext instance InitialContext context = new InitialContext(); //Obtaining a default ManagedExecutorService //Using the java:comp/DefaultManagedExecutorService ManagedExecutorService executorService = (ManagedExecutorService) context.lookup("java:comp/DefaultManagedExecutorService"); //Using annotation @Resource(lookup="java:comp/DefaultManagedExecutorService") ManagedExecutorService executor;
In the rest of the post I will be using the method invocation approach.
Submitting tasks using the submit() method
Lets look at an example code which submits both Callable tasks and Runnable tasks:
protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException, NamingException, InterruptedException, ExecutionException { response.setContentType("text/html;charset=UTF-8"); final PrintWriter out = response.getWriter(); try { out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head>"); out.println("<title>Servlet ConcurrencyDemo</title>"); out.println("</head>"); out.println("<body>"); out.println("<h1>Servlet ConcurrencyDemo at " + request.getContextPath() + "</h1>"); out.println("</body>"); out.println("</html>"); //Create a InitContext instance InitialContext context = new InitialContext(); //Obtaining a default ManagedExecutorService //Using the java:comp/DefaultManagedExecutorService ManagedExecutorService executorService = (ManagedExecutorService) context.lookup("java:comp/DefaultManagedExecutorService"); out.println("MethodExecutorService#submit(Callable)<br/>"); //Using the submit method to submit a Callable task. Future<String> result = executorService.submit(new Callable<String>() { @Override public String call() throws Exception { StringBuilder builder = new StringBuilder(); for (int i = 0; i < 10; i++) { builder.append("String number ").append(i).append("<br/>"); } return builder.toString(); } }); //retrieving the result from the callable task completion. out.println("Output from the Callable invoked using <code>submit()</code>: <br/>" + result.get()); //Using the submit method to submit a Runnable task which doesn't have any return value. out.println("ManagedExecutorService#submit(Runnable)<br/>"); executorService.submit(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { out.println("String number " + i+"<br/>"); } } }); } finally { out.close(); } }
The above processRequest()
method can be invoked from the doGet() and doPost() methods of the servlet which you have crated. And executing the above code on a servlet gives the following output:
Submitting tasks using invokeAll() method
The invokeAll() method is used to invoke multiple Callback tasks at the same time. A list of Callback tasks is created and is passed as a parameter to the invokeAll() method. Lets look at the code below:
protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException, NamingException, InterruptedException, ExecutionException { response.setContentType("text/html;charset=UTF-8"); final PrintWriter out = response.getWriter(); try { /* TODO output your page here. You may use following sample code. */ out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head>"); out.println("<title>Servlet ConcurrencyDemo</title>"); out.println("</head>"); out.println("<body>"); out.println("<h1>Servlet ConcurrencyDemo at " + request.getContextPath() + "</h1>"); out.println("</body>"); out.println("</html>"); //Create a InitContext instance InitialContext context = new InitialContext(); //Obtaining a default ManagedExecutorService //Using the java:comp/DefaultManagedExecutorService ManagedExecutorService executorService = (ManagedExecutorService) context.lookup("java:comp/DefaultManagedExecutorService"); /** * Using the invokeAll method */ out.println("MethodExecutorService#invokeAll(Callable)<br/>"); List<Callable<String>> callableList = new ArrayList<>(); callableList.add(new Callable<String>() { @Override public String call() throws Exception { return "Done processing Callable Task 1"; } }); callableList.add(new Callable<String>() { @Override public String call() throws Exception { Thread.sleep(10000); return "Done processing Callable Task 2"; } }); callableList.add(new Callable<String>() { @Override public String call() throws Exception { Thread.sleep(1000); return "Done processing Callable Task 3"; } }); out.println("Output from the Callables invoked using <code>invokeAll()</code>: <br/>"); List<Future<String>> futuresList = executorService.invokeAll(callableList); for(Future<String> future: futuresList){ out.println(future.get()+"<br/>"); } } finally { out.close(); } }
The above method can be invoked from any servlet and the resulting output is:
Submitting tasks using invokeAny() method
The invokeAny() method accepts a list of Callable tasks just like the invokeAll() method. The difference is that once the invokeAny() method receives result from any one of the tasks, then it suspends the rest of the tasks. Lets look at the code below:
protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException, NamingException, InterruptedException, ExecutionException { response.setContentType("text/html;charset=UTF-8"); final PrintWriter out = response.getWriter(); try { /* TODO output your page here. You may use following sample code. */ out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head>"); out.println("<title>Servlet ConcurrencyDemo</title>"); out.println("</head>"); out.println("<body>"); out.println("<h1>Servlet ConcurrencyDemo at " + request.getContextPath() + "</h1>"); out.println("</body>"); out.println("</html>"); //Create a InitContext instance InitialContext context = new InitialContext(); //Obtaining a default ManagedExecutorService //Using the java:comp/DefaultManagedExecutorService ManagedExecutorService executorService = (ManagedExecutorService) context.lookup("java:comp/DefaultManagedExecutorService"); List<Callable<String>> callableList = new ArrayList<>(); callableList.add(new Callable<String>() { @Override public String call() throws Exception { return "Done processing Callable Task 1"; } }); callableList.add(new Callable<String>() { @Override public String call() throws Exception { Thread.sleep(10000); return "Done processing Callable Task 2"; } }); callableList.add(new Callable<String>() { @Override public String call() throws Exception { Thread.sleep(1000); return "Done processing Callable Task 3"; } }); out.println("MethodExecutorService#invokeAny(Callable)<br/>"); out.println("Output from the Callables invoked using <code>invokeAll()</code>: <br/>"); String invokeAnyResult = executorService.invokeAny(callableList); out.println(invokeAnyResult+"<br/>"); } finally { out.close(); } }
In the above screenshot you can see that only one result if obtained though 3 tasks were submitting for execution. The other two tasks had sleep() method invoked which delayed their completion.
With this I have covered the different ways of submitting tasks for execution using the ManagedExecutorService interface. If you want to run and try out the above code, please include the processRequest() method into your servlet class and invoke the processRequest() from doGet() or doPost() methods.
Note: Dont try to work out all the above examples i.e submit(), invokeAll() and invokeAny() together as it will not give right printing order due to the difference in Thread scheduling. Hence I have discussed them separately.