API de simultaneidade Java EE

Olá pessoal!

E aqui nos deliciamos com pães e lançamos o segundo fluxo do curso "Java Enterprise Developer" . O criador permanente e professor do curso - Vitaly Ivanov , escreveu um artigo sobre este assunto, que, esperamos, lhe parecerá útil :)

Então vamos :)

Este artigo explora a API de Especificação de Concorrência JavaEE ( JSR 236 ), que define o padrão para tarefas paralelas em um contêiner JavaEE usando o conceito de recursos gerenciados. O lançamento da sétima versão do JavaEE tornou possível executar tarefas paralelas em contêineres Enterprise, fornecendo ao desenvolvedor ferramentas e utilitários convenientes para trabalhar com multitarefa. Até aquele momento, toda a multitarefa era deixada para a implementação específica do servidor de aplicativos usado, que decide independentemente sobre a otimização das tarefas. A violação desse princípio foi considerada uma prática recomendada na construção da arquitetura de aplicativos corporativos. Como resultado, o desenvolvedor não foi recomendado para criar novos threads e, às vezes, esse comportamento no nível do contêiner era proibido.


O bean corporativo não deve tentar gerenciar encadeamentos. O enterprise bean não deve tentar iniciar, parar, suspender ou retomar um encadeamento ou alterar a prioridade ou o nome de um encadeamento. O enterprise bean não deve tentar gerenciar grupos de encadeamentos.

(tradução livre do autor: os EJBs não devem tentar gerenciar encadeamentos, ou seja, tentar iniciar, parar, pausar e restaurar sua execução, alterar a prioridade ou alterar o nome de um encadeamento. Além disso, os EJBs não devem tentar gerenciar grupos de encadeamentos.

De fato, proibir a criação de seus próprios encadeamentos em contêineres JavaEE é bastante problemático; no entanto, com essa abordagem, os serviços em segundo plano do contêiner não podem garantir a correção de seu trabalho. Por exemplo, o fechamento de uma transação após a conclusão do método EJB poderia funcionar incorretamente se as tarefas fossem iniciadas em um novo encadeamento usando os herdadores Threads (ou implementações Runnable) do JavaSE. Além disso, o uso dos tipos básicos de interface fornecidos pela API do Executor, como ExecutorService e ScheduledExecutorService, quando criados por meio dos métodos estáticos da classe Executors, levaria a possíveis erros e interromperia a execução dos serviços de contêiner.

Das ferramentas recomendadas pela especificação JavaEE para execução assíncrona de tarefas, o desenvolvedor ainda precisava usar EJBs sem estado / sem estado / assíncronos e / ou beans acionados por mensagens, cujos recursos são suficientes para um certo intervalo de tarefas e, mais importante, cujo gerenciamento é inicialmente completo e completamente controlado pelo servidor de aplicativos, ou seja, um contêiner EJB.

No entanto, como observado anteriormente, graças ao JSR 236 , surgiram recursos gerenciados por contêiner que implementam suporte à execução de tarefas multithreading e assíncrona, expandindo os recursos do pacote java.util.concurrent do JavaSE. Para a pilha JavaEE, as classes de recursos gerenciados estão localizadas no pacote javax.enterprise.concurrent , e o acesso aos objetos dessas classes é feito através da injeção de recursos usando a anotação @Resource ou através do contexto JNDI (em particular, InitialContext). Ao mesmo tempo, foram adicionadas as possibilidades de usar objetos Future / ScheduledFuture / CompletableFuture familiares a um ambiente multithread dentro de aplicativos JavaEE.

Portanto, existem letras suficientes e vamos examinar cada um dos recursos gerenciados fornecidos pela especificação de um ponto de vista prático, nomeadamente no contexto do uso do aplicativo no código do aplicativo, bem como do ponto de vista da configuração de recursos usando o servidor de aplicativos Glassfish 5 como exemplo.

Bem, a classe ManagedExecutorService foi a primeira a ser considerada, o que (já compreendendo pelo nome) estende os recursos do JavaSE familiar fora da caixa ExecutorService e foi projetado para execução assíncrona de tarefas no ambiente JavaEE.

Para configurar não apenas esse tipo de ExecutorService no servidor de aplicativos Glassfish, você deve consultar o arquivo de configuração domain.xml, cuja localização é determinada pelo diretório $ {GLASSFISH_HOME} / domains / <domainname> / config. Um fragmento deste arquivo é apresentado abaixo:

 <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> 

Acessando a interface do painel de administração do Glassfish 5, configurando

ManagedExecutorService é o seguinte:



Esta seção permite a criação de novos recursos do mesmo tipo, gerenciamento de recursos existentes, exclusão e bloqueio e desbloqueio.

Para os fãs da administração do console no Glassfish, é apresentado um utilitário asadmin poderoso, usando o comando create-managed-executor-service nele, é possível criar novos recursos ManagedExecutorService:



No código do aplicativo, é mais conveniente usar a injeção de recurso para obter uma referência ao objeto criado pelo ManagedExecutorService, mas você também pode usar as ferramentas JNDI, como mostrado abaixo:

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

Gostaria de chamar a atenção do leitor para o fato de que o parâmetro de pesquisa é opcional para a anotação @Resource e, se não for definido pelo desenvolvedor no código do aplicativo, o contêiner __default recursos padrão com o prefixo __default em seu __default . Nesse caso, para o desenvolvedor, o código se torna ainda mais conciso:

 @Resource ManagedExecutorService executor; 

Após receber uma referência a esse objeto, usando os métodos execute() e submit() , você pode executar tarefas implementando a interface Runnable ou Callable dentro do contêiner.

Passando a um exemplo, eu gostaria de observar que, entre toda a variedade de casos possíveis, as mais interessantes são as tarefas executadas em um ambiente JavaEE distribuído e nas quais é importante fornecer suporte transacional em um ambiente multithread. Como você sabe, o JavaEE desenvolveu a especificação JTA (Java Transaction API), que permite determinar os limites de uma transação iniciando-a explicitamente com o método begin() e finalizando com os métodos commit() , confirmando alterações ou rollback() , que reverte as ações executadas.

Considere um exemplo de tarefa que retorna uma mensagem de uma lista de cem itens por índice em uma transação do usuário:

 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 () { … } } 

O código para o servlet que exibe uma mensagem da lista em um índice selecionado aleatoriamente:

 @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(); } } } 

O próximo a ser considerado é o recurso ManagedScheduledExecutorService, cujo principal objetivo é planejar tarefas repetidas em intervalos regulares ou que exijam execução atrasada.



Do ponto de vista da configuração desse recurso por meio do console de administração do GlassFish, nenhuma alteração especial foi encontrada em comparação com o tipo anterior:



Para criar rapidamente um recurso do tipo ManagedScheduledExecutorService, asadmin possui o asadmin create-managed-scheduled-executor-service



No código do aplicativo, ainda usamos a injeção de recurso:

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

Os principais métodos para executar tarefas para esse tipo de ExecutorService são o schedule() , que recebe tarefas do tipo Runnable ou Callable como entrada, e scheduleAtFixedRate() , que determina adicionalmente o atraso inicial da tarefa e define o intervalo de repetição no TimeUnit (segundos, minutos etc.) .).

O caso anterior pode ser reescrito da seguinte maneira:

 @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(); } } } 

Além disso, a API de simultaneidade para o ambiente Enterpise fornece a capacidade de criar fluxos controlados. Para essas tarefas, você deve usar os recursos de um encadeamento gerenciado que implementa sua funcionalidade por meio da classe ManagedThreadFactory de mesmo nome e também é acessada por meio do serviço JNDI:

 @Resource ManagedThreadFactory factory; 

A janela de administração do console do Glassfish parece "antiquada":



Usando uma fábrica de encadeamentos gerenciados, torna-se possível não apenas fornecer ao contêiner mecanismos de controle de fluxo, mas também inicializar as propriedades dos encadeamentos gerados: definir nomes e determinar prioridades, que no futuro podem simplificar bastante a procura de problemas ao analisar um despejo de encadeamento, detectando facilmente a ordem de execução dos encadeamentos previamente nomeados.

No nosso caso, definimos a classe do fluxo que exibe informações sobre um amigo com quem esta tarefa está inextricavelmente vinculada ao 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); } } 

Deixe o servlet iniciar o encadeamento e relate isso para a saída:

 @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."); } } 

Voltando ao recurso final do JavaEE no campo de multithreading - Serviços de Contexto, note-se que esses serviços criam objetos de proxy contextual dinâmico. Todos conhecemos os recursos de proxies dinâmicos do JavaSE ( java.lang.reflect.Proxy ), que permitem gerar uma implementação dinâmica das interfaces necessárias, cujos recursos são usados ​​ativamente para criar conexões de banco de dados e gerenciamento de transações, são usados ​​para todos os tipos de interceptadores de AOP, etc. Além disso, para proxies criados por meio dos serviços de contexto JavaEE, supõe-se que eles possam trabalhar dentro da estrutura de um contexto JNDI, contexto de segurança e classe de contêiner comuns.

Para conectar o serviço, basta usar o código:

 @Resource ContextService service; 

Do ponto de vista da administração e configuração desse recurso, tudo é extremamente familiar e semelhante aos tipos já considerados:



Abaixo está um exemplo de um encadeamento que inicia uma tarefa de proxy no contexto de um contêiner:

 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) { … } } 

Bean EJB sem estado para criar proxies contextuais:

 @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); } } 

E, finalmente, o código para o servlet que executa a tarefa:

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

Na verdade, isso encerra os recursos do desenvolvedor JavaEE para trabalhar em um ambiente multithread. Graças a eles, todos os processos e serviços que ocorrem no contêiner estarão sob controle estrito do servidor, coordenando seu trabalho e não violando a ordem de execução usual. Para os objetivos do desenvolvedor corporativo, esses recursos costumam ser suficientes e na oitava versão essa API não mudou.

O FIM

Como sempre, estamos aguardando perguntas e comentários e não deixe de conferir Vitaly para uma aula aberta , onde ele também pode fazer perguntas e ouvir / participar do tópico “CDI em ação @

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


All Articles