Tratamento Assíncrono em Java
Antes de examinar o Spring @Async, os conceitos de síncrono, assíncrono e multithread são essenciais. Assumindo que você esteja familiarizado com esses conceitos, vamos explorar a maneira como o tratamento assíncrono é feito em Java puro por meio de código. Se você já está familiarizado com threads em Java, pode pular este capítulo.
Se criarmos uma função que recebe uma mensagem e simplesmente a imprime usando a abordagem síncrona, o código ficaria como mostrado acima. Se convertermos isso para uma abordagem assíncrona com multithreading, o código-fonte pode ser escrito da seguinte maneira.
No entanto, este método é muito perigoso porque não podemos gerenciar as threads. Por exemplo, se 10.000 chamadas forem feitas simultaneamente, 10.000 threads precisarão ser criadas em um curto período de tempo. O custo de criar threads não é insignificante, então isso afeta negativamente o desempenho do programa e, em alguns casos, pode até resultar em um erro OOM. Portanto, precisamos implementar uma Thread Pool para gerenciar as threads, e o Java fornece a classe ExecutorService para isso.
Limitamos o número total de threads para 10 e agora podemos executar corretamente o tratamento assíncrono do tipo multithreading que desejamos. No entanto, com este método, precisamos aplicar o método submit() do ExecutorService a cada método que queremos tratar assincronamente, o que requer modificações repetitivas. Em outras palavras, se quisermos converter um método originalmente escrito com lógica síncrona para assíncrono, a alteração da lógica do próprio método é inevitável.
Spring @Async
Método Simples
Basta anexar a anotação @EnableAsync à classe Application e a anotação @Async ao método com a lógica síncrona que queremos tratar assincronamente. No entanto, este método tem o problema de não gerenciar threads. Isso ocorre porque a configuração padrão do @Async usa o SimpleAsyncTaskExecutor, que não é uma pool de threads, mas sim uma função que cria threads.
Usando uma Pool de Threads
Primeiro, removemos o @EnableAsync da classe Application. Se a classe Application tiver a configuração @EnableAutoConfiguration ou @SpringBootApplication, ela lerá as informações do bean threadPoolTaskExecutor (que criaremos abaixo) da classe SpringAsyncConfig configurada em tempo de execução.
Podemos definir os tamanhos de core e max. Nesse caso, podemos esperar que ele execute com o tamanho de core inicial e, se não conseguir lidar com mais tarefas, o número de threads aumentará até o tamanho de max, mas não é assim que funciona.
Internamente, ele cria um LinkedBlockingQueue com tamanho Integer.MAX_VALUE e, se as threads com tamanho core não conseguirem lidar com as tarefas, elas ficarão em espera na fila. Se a fila ficar cheia, ele criará threads com tamanho max para lidar com as tarefas.
Se você achar incômodo definir o tamanho da fila como Integer.MAX_VALUE, poderá definir queueCapacity. Com a configuração acima, ele lidará inicialmente com 3 threads e, se a velocidade de processamento diminuir, as tarefas serão colocadas em uma fila com 100 espaços. Se houver mais solicitações, ele criará até 30 threads para lidar com as tarefas.
Depois de configurar a pool de threads, podemos anexar o nome desse bean ao método com a anotação @Async.
Se quisermos configurar vários tipos de pools de threads, podemos criar vários métodos de criação de beans, como threadPoolTaskExecutor(), e, ao configurar o @Async, inserir o bean da pool de threads desejado.
Formato de Retorno com base no Tipo de Retorno
**Caso não haja valor de retorno**
Este é o caso em que o método que precisa ser processado assincronamente não precisa retornar nenhum resultado de processamento. Nesse caso, você pode definir o tipo de retorno da anotação @Async como void.
**Caso haja valor de retorno**
Os tipos Future, ListenableFuture e CompletableFuture podem ser usados como tipos de retorno. O formato de retorno do método assíncrono pode ser encapsulado com new AsyncResult().
[Future]
future.get() tem a função de esperar pelo resultado da solicitação por meio de bloqueio. Portanto, torna-se uma abordagem de bloqueio assíncrona, o que resulta em baixo desempenho. Normalmente, o Future não é usado.
[ListenableFuture]
O ListenableFuture permite que as tarefas sejam processadas de forma não bloqueante por meio de callbacks. O primeiro parâmetro do método addCallback() define o método de callback de conclusão da tarefa, e o segundo parâmetro define o método de callback de falha da tarefa. Observe que, como definimos o número de threads básicas da pool de threads como 3, você pode ver que as mensagens "Task Start" são exibidas três vezes no início.
[CompletableFuture]
Embora o ListenableFuture seja suficiente para implementar a lógica não bloqueante, se precisarmos de um callback dentro de outro callback, isso pode levar a um código muito complexo, chamado de "callback hell".
<span class="image-inline ck-widget" contenteditable="false"><img src="https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F9f152db8-c015-43cd-bf13-85c594f5f218%2FUntitled.png?table=block&id=268ac0bc-ca7b-4bcb-b11b-ac611a5038b2&spaceId=b453bd85-cb15-44b5-bf2e-580aeda8074e&width=2000&userId=80352c12-65a4-4562-9a36-2179ed0dfffb&cache=v2" alt="Untitled" style="aspect-ratio:2000/1455;" width="2000" height="1455"></span>
Claro, como não vamos cobrir o CompletableFuture em detalhes nesta sessão, se você estiver curioso sobre o código que lida com callbacks complexos, consulte o link de origem abaixo.
A legibilidade é melhor do que a definição de callback do ListenableFuture e ele executa perfeitamente a função não bloqueante. Portanto, recomendamos o uso do CompletableFuture quando um valor de retorno for necessário ao usar o @Async.
Vantagens do @Async
Os desenvolvedores podem escrever métodos usando a abordagem síncrona e, se precisarem de uma abordagem assíncrona, basta anexar a anotação @Async ao método. Isso permite que você crie código bem mantido para métodos síncronos e assíncronos.
Precauções com o @Async
Para usar a função @Async, precisamos declarar a anotação @EnableAsync. No entanto, se não fizermos nenhuma configuração adicional, ela funcionará no modo proxy. Em outras palavras, todos os métodos assíncronos que funcionam com a anotação @Async seguirão as restrições do Spring AOP. Para obter mais detalhes, consulte esta postagem.
- Mesmo que você anexe o @Async a um método privado, o AOP não funcionará.
- Se você chamar métodos dentro do mesmo objeto, o AOP não funcionará.
Comentários0