Concorrência com Java EE | MATERA Systems
MATERA Systems > Tecnologia > Concorrência com Java EE
Concorrência com Java EE
Por: Mario Eduardo Giolo Postado em: 29 de junho de 2015 Tecnologia
Olá, neste post vamos falar um pouco sobre da API para processamento concorrente em aplicações Java EE, a "Concurrency Utilities for Java EE", disponibilizada através da JSR236 como parte da especificação do Java EE 7.
Nao é muito comum vermos projetos que demandem um conhecimento profundo na utilização de Threads e outros tipos de processamento concorrente, porém quando a necessidade surge é bom conhecermos bem as APIs e as ferramentas que o universo Java nos oferece para trabalhar com processamento concorrente.
Na versão 5 do Java, foi disponibilizada uma API com intuito de endereçar esta necessidade, denominada "Concurrency Utilities" (sob a JSR-166), que vai além do uso da API básica de Threads, trazendo uma série de elementos que auxiliam o trabalho do desenvolvedor, e facilitam a criação de soluções mais robustas.
Uma das recomendações ao se trabalhar com Servidores de Aplicações, é que as aplicações não façam uso dos recursos de concorrência disponíveis através do Java SE, como "java.lang.Thread" e "java.util.Timer", e falando de uma forma geral, sempre evitar a utilização dos recuros que não sejam gerenciados e disponibilizados pelos próprios Servidores.
Assim uma especificação para tratar processamento paralelo com foco na plataforma Java EE era uma necessidade, e que foi atendida na versão 7 do Java EE, trazendo novos elementos através por meio de uma nova API que segue os moldes da API de concorrência definida para uso do Java SE.
A API teve como objetivo:
Utilizar os serviços e funcionalidades já existentes na plataforma Java EE, propondo uma API simples e flexível.
Manter a consistência entre as APIs das plataformas Java EE e Java SE, sendo a API uma extensão da versão Java SE.
Permitir a fácil adição de processamento paralelo as aplicaões Java EE já existentes.
Dar suporte aos padrões de concorrência simples e mais avançados, sem deixar de lado a escalabilidade.
API fornece é composta por algumas Interfaces primárias que extendem outras interfaces presentes no JSE, disponíveis através do pacote "javax.enterprise.concurrent", sendo:
ManagedExecutorService:
Versão da Interface "ExecutorService", para servidores de aplicação.
Permite a execução de tarefas de uma aplicação, utilizando um conjunto de "Threads" gerenciados pelo próprio Container Java EE, e não faz controle de Transação, sendo que cada Tarefa deve ser responsável pelas suas transações.
O seu uso é recomendado para execução de tarefas mais curtas e simples.
Como este é um recurso do servidor, os métodos envolvendo o ciclo de vida não estão disponíveis, e caso sejam chamados lançam a Exceção "IllegalStateException".
ManagedScheduledExecutorService:
Versão da Interface "ScheduledExecutorService", para servidores de aplicação, também extende de "ManagedExecutorService".
Permite a execução de tarefas de forma agendada, periodicamente, ou serem executadas após passado algum tempo.
Versão da interface "ThreadFactory", para Servidores de Aplicação.
É possível obter uma ou mais instâncias dos objetos mencionados acima através de JNDI (pelo "look-up"), ou fazer a injeção no código da sua aplicação através do uso da anotação "@Resource".
Se desejar, é possível definir o nome e algumas outras configurações para cada objeto a ser utilizado na aplicação, ou utilizar a configuração padrão que vêm junto ao servidor de aplicação. Para os nossos exemplos vamos utilizar as definições padrões, e o servidor de aplicação Wildfly.
Para que um determinado código seja executado de forma assíncrona, ele precisa estar contido em uma classe que implemente a classe "Callable", ou "Runnable", vamos criar uma classe que representará uma simples tarefa a ser executada:
package com.matera.exemplos.concurrency;
public SimpleTask(int id) {
System.out.println("Iniciando a nossa tarefa, ID: " + id);
Thread.sleep(3000); //aguarda 3 segundos
System.out.println("Problemas ao fazer o processo " + id +" aguardar 3 segs.");
System.out.println("Finalizando a nossa tarefa, ID: " + id);
return "MySimpleTask [ ID: " + id + "] ";
Esta simples tarefa terá um identificador, e estará completa em 3 segundos.
Vamos criar um "Servlet" de exemplo, que cria e submete 3 destas tarefas, para execução, utilizando um objeto "ManagedExecutorService", injetado pelo Servidor, e cada tarefa terá como identificador, a ordem em que foi criada pelo Servlet:
@WebServlet("/SubmitServlet")
writer.println("<title>Servlet que submete as tarefas</title>");
writer.println("Criando e Submetendo as tarefas... ");
for (int i= 1; i <= 3; i++){
executor.execute(new SimpleTask(i));
writer.println("<br> Tarefa submetida para execução - " + i);
writer.println("<br> Processo concluído.");
Ao acessarmos o endereço do nosso Servlet, no Servidor, veremos a saída:
Criando e Submetendo as tarefas...
Tarefa submetida para execução - 1
Tarefa submetida para execução - 2
Tarefa submetida para execução - 3
O registro da nossa saída padrão, no Servidor, deverá conter o registro das execuções das tarefas, como exemplo:
12:26:31,515 INFO  [stdout] (EE-ManagedExecutorService-default-Thread-1) Iniciando a nossa tarefa, ID: 1
12:26:31,515 INFO  [stdout] (EE-ManagedExecutorService-default-Thread-3) Iniciando a nossa tarefa, ID: 3
12:26:31,515 INFO  [stdout] (EE-ManagedExecutorService-default-Thread-2) Iniciando a nossa tarefa, ID: 2
12:26:34,515 INFO  [stdout] (EE-ManagedExecutorService-default-Thread-1) Finalizandoa  nossa tarefa, ID: 1
12:26:34,515 INFO  [stdout] (EE-ManagedExecutorService-default-Thread-3) Finalizandoa  nossa tarefa, ID: 3
12:26:34,517 INFO  [stdout] (EE-ManagedExecutorService-default-Thread-2) Finalizandoa  nossa tarefa, ID: 2
Observe que o Servidor utilizou 3 diferentes "threads" para execução das nossas tarefas, e não há qualquer ordem garantida ou definida para este caso.
A nossa tarefa simples (SimpleTask), após ser submetida para execução, não devolve qualquer resultado para quem a submeteu para execução, para que possamos ter algum retorno da execução das tarefas, devemos utilizar uma classe que implemente a interface "Callable", vamos ver um exemplo:
public class SimpleCallableTask implements Callable<Integer>{
public SimpleCallableTask(int id) {
System.out.println("Iniciando o callable, ID: " + id);
// Retorna um valor aleatório entre 1 e 10
Integer retorno = rand.nextInt(10) + 1;
System.out.println("Devolvendo o resultado da nossa tarefa, ID: " + id);
return "MySimpleCallableTask [ ID: " + id + "] ";
A idéia é que esta classe retorne um valor randômico entre 1 e 10, que será apresentado posteriormente pelo HTML gerado como resultado da chamada a um novo Servlet.
Vamos criar o Servlet que faz uso desta tarefa, como exemplo:
public class SubmitCallableServlet extends HttpServlet {
writer.println("Criando e Submetendo uma tarefa que terá retorno... ");
Future<Integer> retorno = executor.submit(new SimpleCallableTask(1));
writer.println("<br> Tarefa submetida para execução.");
writer.println("<br> Obtendo os retornos: ");
valor = retorno.get(5, TimeUnit.SECONDS);
writer.println("<br> Valor retornado (randômico de 1 a 10): " + valor);
writer.println("<br> Tarefa foi interrompida durante a espera pelo resultado.");
writer.println("<br> Ocorreram problemas durante execução da Tarefa.");
writer.println("<br> Tempo de espera excedido.");
Observe que este Servlet ao ser acionado pelo método "GET", faz a criação da nossa tarefa (SimpleCallableTask) e ao realizar a submissão, recebe como retorno um objeto "Future", que possibilita o acesso ao valor que será gerado pela nossa tarefa, em uma outra thread:
Através do objeto "retorno" o valor pode ser acessado através do método "get", que no exemplo, definimos um determinado tempo de espera para este retorno, que caso seja excedido, lançara uma exceção, é importante observar que o objeto denominado "retorno" possibilita que a nossa thread principal (executando o código do Servlet) acesse um valor que será gerado por uma outra thread definida e gerenciada pelo servidor.
Se desejarmos, podemos executar as nossas tarefas com uma determinada priodicidade, para isto podemos utilizar a classe "ManagedScheduledExecutorService", e como exemplo um Servlet que cria os agendamentos como desejado:
@WebServlet("/ScheduledServlet")
public class ScheduledServlet extends HttpServlet {
//executor que permite gerenciarmos o agendamento das tarefas.
@Resource(name = "DefaultManagedScheduledExecutorService")
//Demais lógicas desejadas, obtenção do objeto "writter"...
Runnable task1 = new SimpleTask(1);
Runnable task2 = new SimpleTask(2);
Runnable task3 = new SimpleTask(3);
Callable task4 = new SimpleCallableTask(4);
executor.schedule(task1, 5, TimeUnit.SECONDS);
executor.scheduleAtFixedRate(task2, 3, 5, TimeUnit.SECONDS);
executor.scheduleWithFixedDelay(task3, 2, 2, TimeUnit.SECONDS);
Future retorno = executor.schedule(task4, 1, TimeUnit.SECONDS);
writer.println("Numero randomico gerado:" + retorno.get());
Vale observar que os método "schedule", "scheduleAtFixedRate" e "scheduledWithFixedDelay" podem submeter a execução de tarefas com ou sem retorno, como no exemplo acima, mostrando a submissão da tarefa "task4″.
Fazendo uso da implementação de "ManagedScheduledExecutorService" é possível um "objeto" que represente a interface "Trigger" (javax.enterprise.concurrent.Trigger), que será responsável pela execução da nossa tarefa.
A interface "Trigger" define os métodos "getNextRunTime(…)" (acionado ao final de uma tarefa) e "skipRun(…)" (acionado antes de iniciar a tarefa agendada, no momento do agendamento), através destes métodos podemos definir quando será a próxima execução desta tarefa e se a execução deve ser ignorada. Vejamos um exemplo:
import javax.enterprise.concurrent.LastExecution;
import javax.enterprise.concurrent.Trigger;
public class SimpleTrigger implements Trigger{
private int numeroDeExecucoes = 0;
public Date getNextRunTime(LastExecution lastExecution, Date scheduledTime) {
//Para a primeira execução, não há informação de ultima execução
Date inicioUltimaExecucao = lastExecution != null ? lastExecution.getScheduledStart() : scheduledTime;
Calendar proximaExecucao = Calendar.getInstance();
proximaExecucao.setTime(inicioUltimaExecucao);
//Adiciona 1 minuto a contar da ultima execução
proximaExecucao.add(Calendar.MINUTE, 1);
numeroDeExecucoes++;
return proximaExecucao.getTime();
public boolean skipRun(LastExecution lastExecution, Date scheduledTime) {
//Ignora caso tenha sido executado mais de 3 vezes.
return numeroDeExecucoes > 3;
Neste exemplo acima, após cada execução de uma tarefa, o horário da próxima execução ocorrerá após 1 minuto, e a mesma tarefa será executada no máximo 3 vezes.
O uso desta regra para controle da execução das tarefas, pode ser feito da seguinte forma:
executor.schedule(new SimpleTask(5), new SimpleTrigger());
Um outro recurso definido pela API, envolve a criação de um "Listener" ou objeto que escuta cada alteração de estado de uma tarefa, através da implementação da interface "javax.enterprise.concurrent.ManagedTaskListener".
Nos exemplos mencionados acima, todas as threads que fizeram a execução das nossas tarefas, foram threads criadas pelo próprio servidor de aplicação, se desejarmos podemos solicitar a criação de novas threads, através da interface "ManagedThreadFactory", vejamos um exemplo:
* Servlet implementation class ThreadFactoryServlet
@WebServlet("/ThreadFactoryServlet")
public class ThreadFactoryServlet extends HttpServlet {
writer.println("<title>Servlet que submete tarefas</title>");
writer.println("Criando e Submetendo a tarefa com uma nova thread... ");
Runnable task = new SimpleTask(1);
Thread novaThread = threadFactory.newThread(task);
//Nomeando a Minha Thread
novaThread.setName("Minha Thread");
Para o código acima, o resultado a ser exibido pelo servidor será:
16:29:07,076 INFO  [stdout] (Minha Thread) Iniciando a nossa tarefa, ID: 1
16:29:10,077 INFO  [stdout] (Minha Thread) Finalizando a nossa tarefa, ID: 1
Estes são alguns exemplos bem didáticos a respeito do uso de concorrência e alguns recursos do Java EE 7, não deixem de dar uma olhada nos links abaixo, e na especificação para conhecer todos os demais recursos que a API nos oferece.
https://jcp.org/en/jsr/detail?id=236 – Especificação
http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/concurrency/Concurrency.html – Tutorial demonstrando mais recursos da API em uma simples sistema de busca em arquivos.
http://docs.oracle.com/javaee/7/api/javax/enterprise/concurrent/package-summary.html – Detalhes do pacote contendo as classes referentes a API
http://pt.slideshare.net/reza_rahman/java-ee-concurrency-utilities – Resumo dos recursos.
Tags:Concorrência, JavaEE, JSR236, ManagedExecutorService, ManagedScheduledExecutorService, ManagedThreadFactory, paralelismo, Performance, Servlet, Threads