📜 ⬆️ ⬇️

Java EE Concurrency API

Hello!

And here we are indulging with buns, launching the second stream of the Java Enterprise Developer course. The permanent creator and teacher of the course, Vitaly Ivanov , wrote an article on this subject even which we hope will seem useful to you :)

So let's go :)
')
This article explores the JavaEE Concurrency API ( JSR 236 ) API, which defines the standard for executing parallel tasks in a JavaEE container using the concept of managed resources. The release of the seventh version of JavaEE made it possible to run parallel tasks in Enterprise containers, providing the developer with convenient tools and utilities for working with multitasking. Until that very moment, all multitasking was left at the mercy of the specific implementation of the application server used, independently deciding on the optimization of task execution. Violation of this principle was regarded as a bad practice of building an enterprise application architecture. As a result, the developer was not recommended to create new threads, and sometimes similar behavior was prohibited at the container level.


Manage threads. It is a priority or a name. Enterprise bean

(free translation of the author: EJBs should not try to control threads, namely, try to start, stop, suspend, or restore their execution, or change the priority or change the name of the stream. Also, EJBs should not try to manage groups of threads).

In fact, prohibiting the creation of your own threads in JavaEE containers is problematic, but with this approach, the “background” container services cannot guarantee the correctness of their work. For example, closing a transaction upon the completion of an EJB method could potentially work incorrectly in the case of running tasks in a new thread using Threads heirs (or Runnable implementations) from JavaSE. Also, the use of basic interface types from the Executor API delivery, such as ExecutorService and ScheduledExecutorService, when created using Executors class static methods, would lead to potential errors and disrupting the order of execution of container services.

From the JavaEE specification-recommended tools for asynchronous execution of tasks, the developer had at his disposal the use of asynchronous Stateless / Statefull EJB and / or Message Driven bins, which have enough capabilities for a certain range of tasks and most importantly that the management of which is entirely and entirely controlled by the application server, namely the EJB container.

However, as noted earlier, thanks to JSR 236 , container-managed resources have emerged that implement support for multithreading and asynchronous task execution, extending the capabilities of the java.util.concurrent package from JavaSE. For the JavaEE stack, the classes of managed resources are located in the javax.enterprise.concurrent package, while access to the objects of these classes is through resource embedding using the @Resource annotation, or through the JNDI context (in particular, InitialContext). At the same time, the possibility of using the Future / ScheduledFuture / CompletableFuture objects, which are usual for a multithreaded environment, inside JavaEE applications was added.

So, quite lyric and let's take a closer look at each of the managed resources provided by the specification from a practical point of view, namely in terms of use in the application code of the application, as well as in terms of configuring resources on the example of the Glassfish 5 application server.

Well, the ManagedExecutorService class is selected first in the review queue, which (already understanding from the name) extends the capabilities of the usual JavaSE ExecutorService and is intended for asynchronous execution of tasks in the JavaEE environment.

To configure within the Glassfish application server, not only this type of ExecutorService, refer to the domain.xml configuration file, whose location is defined by the $ {GLASSFISH_HOME} / domains / <domain_name> / config directory. A fragment of this file is presented below:

 <domain application-root="${com.sun.aas.instanceRoot}/applications" version="25" log-root="${com.sun.aas.instanceRoot}/logs"> <resources> <context-service object-type="system-all" jndi-name="concurrent/__defaultContextService" /> <managed-executor-service object-type="system-all" jndi-name="concurrent/__defaultManagedExecutorService" /> <managed-scheduled-executor-service object-type="system-all" jndi-name="concurrent/__defaultManagedScheduledExecutorService" /> <managed-thread-factory object-type="system-all" jndi-name="concurrent/__defaultManagedThreadFactory" /> </resources> <servers> <server config-ref="server-config" name="server"> <resource-ref ref="concurrent/__defaultContextService" /> <resource-ref ref="concurrent/__defaultManagedExecutorService" /> <resource-ref ref="concurrent/__defaultManagedScheduledExecutorService" /> <resource-ref ref="concurrent/__defaultManagedThreadFactory" /> </server> </servers> </domain> 

By accessing the Glassfish 5 admin panel interface, configuring

ManagedExecutorService looks like this:



In this section, it is allowed to create new resources of the same type, manage existing ones, delete, and also lock and unlock.

For lovers of console administration, Glassfish provides a powerful utility asadmin, using which the create-managed-executor-service command allows you to create new ManagedExecutorService resources:



In the application code, to get a reference to the object created by the ManagedExecutorService, it is more convenient to use resource injection, but you can also use JNDI tools, as shown below:

 @Resource(lookup = "concurrent/OtusExecutorService") ManagedExecutorService executor; InitialContext context = new InitialContext(); ManagedExecutorService managedExecutorServiceWithContext = (ManagedExecutorService) context.lookup( "concurrent/OtusExecutorService"); 

I would like to draw the reader’s attention to the fact that, for the @Resource annotation @Resource the lookup parameter is optional and if it is not defined by the developer in the application code, then the container injects default resources with the prefix __default in the name. In this case, for the developer, the code becomes even more concise:

 @Resource ManagedExecutorService executor; 

After getting the reference to this object, using the execute() and submit() methods, you can run tasks inside the container that implement the Runnable or Callable interface.

Turning to the example, I would like to note that among all the variety of possible cases, the tasks of particular interest are those performed in a distributed JavaEE environment and in which it is important to ensure support for transactionality in a multi-threaded environment. As you know, JavaEE has developed the JTA (Java Transaction API) specification, which allows you to define transaction boundaries, explicitly starting with the begin() method and completing with the commit() methods, committing changes, or rollback() , rolling back the actions taken.

Consider an example of a task that returns a message from a certain list of one hundred elements, by index in a user transaction:

 public class TransactionSupportCallableTask implements Callable<String> { private int messageIndex; public TransactionSupportCallableTask(int messageId) { this. messageIndex = messageId; } public String call() { UserTransaction tx = lookupUserTransaction(); String message = null; try { tx.begin(); message = getMessage(messageIndex); tx.commit(); } catch (Exception e) { e.printStackTrace(); try { tx.rollback(); } catch (Exception e1) { e1.printStackTrace(); } } return message; } private void getMessage(int index) { … } private UserTransaction lookupUserTransaction () { … } } 

The code of the servlet that displays the message on the list at a randomly selected index:

 @WebServlet("/task") public class ManagedExecutorServiceServlet extends HttpServlet { @Resource(lookup = "concurrent/OtusExecutorService") ManagedExecutorService executor; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Future<String> futureResult = executor.submit(new TransactionSupportCallableTask(Random.nextInt(100))); while (!futureResult.isDone()) { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } try { response.getWriter().write("Callable task has received message with following content '" + futureResult.get() + "'"); } catch(Exception e) { e.printStackTrace(); } } } 

The next resource to be selected is the ManagedScheduledExecutorService resource, the main purpose of which is to schedule tasks that are repeated with a certain periodicity or require deferred execution.



From the point of view of configuring this resource through the GlassFish administration console, no special changes were detected in comparison with the previous type:



To quickly create a resource with the ManagedScheduledExecutorService type, the asadmin utility has the create-managed-scheduled-executor-service command



In the application code, we still use resource injection:

 @Resource(lookup = "concurrent/OtusScheduledExecutorService") ManagedScheduledExecutorService scheduledExecutor; 

The main methods for performing tasks for this type of ExecutorService are schedule() , which accepts tasks like Runnable or Callable as input, and scheduleAtFixedRate() , which additionally determines the initial delay in task execution and specifies the interval of repetitions in TimeUnit (seconds, minutes, etc. .).

The previous case can be rewritten as follows:

 @WebServlet("/scheduledTask") public class ManagedScheduledExecutorServiceServlet extends HttpServlet { @Resource(lookup = "concurrent/OtusScheduledExecutorService") ManagedScheduledExecutorService scheduledExecutor; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ScheduledFuture<String> futureResult = scheduledExecutor.schedule( new TransactionSupportCallableTask(Random.nextInt(100)), 5, TimeUnit.SECONDS); while (!futureResult.isDone()) { try { Thread.sleep(50); // Wait } catch (InterruptedException e) { e.printStackTrace(); } } try { response.getWriter().write("Callable task received message with following content '" + futureResult.get() + "'"); } catch ( Exception e) { e.printStackTrace(); } } } 

Also, the Concurrency API for the Enterpise environment provides the ability to create managed threads. For these tasks, you should use the capabilities of a managed thread factory that implements its functionality through the ManagedThreadFactory class of the same name and which is also accessed via the JNDI service:

 @Resource ManagedThreadFactory factory; 

The administration window for the Glassfish console looks “old fashioned”:



Using a managed thread factory, it is possible not only to provide the container with thread control mechanisms, but also to initialize the properties of the threads generated: set names and prioritize, which can seriously simplify the search for problems when dumping threads later, easily finding the order of execution of previously named threads.

In our case, we define the class of the stream that displays information about the friend with which the task is inseparably linked to the console:

 public class SimpleThreadTask implements Runnable { private String friend; public SimpleThreadTask(String friend){ this.friend = friend; } @Override public void run() { System.out.println("Hello, " + friend); } } 

Let the servlet start a thread and report it at the output:

 @WebServlet("/thread") public class ManagedThreadFactoryServlet extends HttpServlet { @Resource ManagedThreadFactory factory; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Thread thread = factory.newThread(new SimpleThreadTask("Otus")); thread.setName("ManagedThreadFromPool"); thread.setPriority(7); thread.start(); response.getWriter().write("Custom thread has been running."); } } 

Turning to the final JavaEE multithreading capabilities of Context Services, it should be noted that these services create dynamic context proxies. We all are well aware of the dynamic proxies from JavaSE ( java.lang.reflect.Proxy ), which allow generating dynamic implementations of the required interfaces, whose capabilities are actively used for the tasks of creating database connections and transaction management, are used for various AOP interceptors and so on. Moreover, for proxies created via JavaEE contextual services, it is supposed to work within the framework of a common JNDI context, a security context and a container class wizard.

To connect the service it is enough to use the code:

 @Resource ContextService service; 

From the point of view of administration and configuration of this resource, everything is extremely familiar and similar to the types already considered:



Below is an example of a thread that runs a proxy task in the context of a container:

 public class SampleProxyTask implements Runnable { @Override public void run() { //  Subject subject = Subject.getSubject(AccessController.getContext()); logInfo(subject.getPrincipals()); //    calculateSmth(); } private void calculateSmth() { … } private void logInfo(Set<Principal> subject) { … } } 

Stateless EJB Bin for creating context proxies:

 @Stateless public class ContextServiceBean { @Resource ContextService service; @Resource ManagedExecutorService executor; public void perform(Runnable task) { Runnable proxy = service.createContextualProxy(task, Runnable.class); executor.submit(proxy); } } 

Finally, the code of the servlet performing the task:

 @WebServlet("/context") public class ContextServiceServlet extends HttpServlet { @Inject ContextServiceBean contextServiceBean; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { contextServiceBean.perform(new SampleProxyTask()); } } 

This actually ends the JavaEE developer’s ability to work in a multi-threaded environment. Thanks to them, all processes and services occurring in the container will be under the strict control of the server, coordinating their work and not disturbing the usual order of execution. For the target tasks of an Enterprise developer, these opportunities are often enough and in the eighth version this API has not changed.

THE END

As always, we are waiting for questions and comments, and be sure to check out Vitaly for an open lesson , where you can also ask questions and listen / participate in the topic “CDI in action @

Source: https://habr.com/ru/post/421809/


All Articles