Concorrência com Java EE

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.
  • ManagedThreadFactory:
    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.

Vamos ver alguns exemplos:

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 class SimpleTask implements Runnable{
	
	private int id;
	
	public SimpleTask(int id) {
		this.id = id;
	}
	
	@Override
	public void run() {
		System.out.println("Iniciando a nossa tarefa, ID: " + id);
		
		try {
			Thread.sleep(3000); //aguarda 3 segundos
			
		} catch (InterruptedException e) {
			e.printStackTrace();
			System.out.println("Problemas ao fazer o processo " + id +" aguardar 3 segs.");
		}

		System.out.println("Finalizando a nossa tarefa, ID: " + id);
	}

	@Override
	public String toString() {
		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:

package com.matera.exemplos.concurrency;

//Imports ... 

@WebServlet("/SubmitServlet")
public class SubmitServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	@Resource(name = "DefaultManagedExecutorService")
	private ManagedExecutorService executor;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		PrintWriter writer = response.getWriter();
		
		writer.println("<!DOCTYPE html>");
		writer.println("<html>");
		writer.println("<head>");
		writer.println("<title>Servlet que submete as tarefas</title>");
		writer.println("</head>");
		writer.println("<body>");		
		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.");
		writer.println("</body>");
		writer.println("</html>");
	}
}

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 
     Processo concluído.

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:

package com.matera.exemplos.concurrency;

import java.util.Random;
import java.util.concurrent.Callable;

public class SimpleCallableTask implements Callable<Integer>{

	private int id; 
	
	public SimpleCallableTask(int id) {
		this.id = id;
	}
	
	@Override
	public Integer call() throws Exception {
		
		System.out.println("Iniciando o callable, ID: " + id);
		
		try {
			Thread.sleep(3000); //Aguarda 3 segundos
					
		} catch (InterruptedException e) {
			e.printStackTrace();
			System.out.println("Problemas ao fazer o processo " + id +" aguardar 3 segs.");
		}
		
		// Retorna um valor aleatório entre 1 e 10
		Random rand = new Random();
		Integer retorno = rand.nextInt(10) + 1; 
		
		System.out.println("Devolvendo o resultado da nossa tarefa, ID: " + id);
		return retorno;
	}

	@Override
	public String toString() {
		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:

@WebServlet("/SubmitServlet")
public class SubmitCallableServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	@Resource(name = "DefaultManagedExecutorService")
	private ManagedExecutorService executor;

	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		PrintWriter writer = response.getWriter();

		writer.println("<!DOCTYPE html>");
		writer.println("<html>");
		writer.println("<head>");
		writer.println("<title>Servlet que submete as tarefas</title>");
		writer.println("</head>");
		writer.println("<body>");		
		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: ");

		Integer valor; 
		try {
			valor = retorno.get(5, TimeUnit.SECONDS);
			writer.println("<br> Valor retornado (randômico de 1 a 10): " + valor);
			
		} catch (InterruptedException e) {
			writer.println("<br> Tarefa foi interrompida durante a espera pelo resultado.");
			e.printStackTrace();

		} catch (ExecutionException e) {
			writer.println("<br> Ocorreram problemas durante execução da Tarefa.");
			e.printStackTrace();

		} catch (TimeoutException e) {
			writer.println("<br> Tempo de espera excedido.");
			e.printStackTrace();
			
		} finally {
			writer.println("<br> Processo concluído.");
			writer.println("</body>");
			writer.println("</html>");
		}

	}
}

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:

Future<Integer> retorno = executor.submit(new SimpleCallableTask(1));

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:

// package, imports... 

@WebServlet("/ScheduledServlet")
public class ScheduledServlet extends HttpServlet {

	//executor que permite gerenciarmos o agendamento das tarefas.
	@Resource(name = "DefaultManagedScheduledExecutorService")
	private ManagedScheduledExecutorService executor;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {		
		
		//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);
		try {
			writer.println("Numero randomico gerado:" + retorno.get());
		 catch (InterruptedException | ExecutionException e) {
			e.printStackTrace();
		}
	}
}

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:

package com.matera.exemplos.concurrency;

import java.util.Calendar;
import java.util.Date;

import javax.enterprise.concurrent.LastExecution;
import javax.enterprise.concurrent.Trigger;

public class SimpleTrigger implements Trigger{

	private int numeroDeExecucoes = 0;
	
	@Override
	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();
	}

	@Override
	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:

package com.matera.exemplos.concurrency;

import java.io.IOException;
import java.io.PrintWriter;

import javax.annotation.Resource;
import javax.enterprise.concurrent.ManagedThreadFactory;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class ThreadFactoryServlet
 */
@WebServlet("/ThreadFactoryServlet")
public class ThreadFactoryServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
   
	@Resource(name = "DefaultManagedThreadFactory")
	private ManagedThreadFactory threadFactory;
	
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		PrintWriter writer = response.getWriter();

		writer.println("<!DOCTYPE html>");
		writer.println("<html>");
		writer.println("<head>");
		writer.println("<title>Servlet que submete tarefas</title>");
		writer.println("</head>");
		writer.println("<body>");		
		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");
		novaThread.start();
		
		writer.println("<br> Processo concluído.");
		writer.println("</body>");
		writer.println("</html>");
	}

	
}

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.

Espero que tenham gostado, e até a próxima!

Links:

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.

 

Por MARIO EDUARDO GIOLO

Postado em: 29 de junho de 2015

Confira outros artigos do nosso blog

REST não é JSON

21 de agosto de 2017

Bruno Sofiato

[Webinar] Profile de aplicações Java com Oracle Mission Control e Flight Recorder

24 de julho de 2017

Danival Calegari

Criando Mocks de serviços REST com SoapUI

27 de junho de 2017

Monise Costa

JavaScript 6: diferença entre var, let e const

09 de maio de 2017

Otávio Felipe do Prado

Deixe seu comentário