Multithreading com Java: Explorando o Poder da Execução Paralela







Em programação, uma thread é uma sequência de instruções executadas independentemente. Em Java, as threads permitem que você execute várias operações simultaneamente em um programa, aproveitando ao máximo os recursos de processamento de múltiplos núcleos em processadores modernos. Uma thread é a unidade básica de execução dentro de um programa e faz parte do processo do programa, mas pode ser executada separadamente.

Java tem suporte nativo para a criação e gerenciamento de threads, permitindo que os desenvolvedores criem programas que podem realizar várias tarefas ao mesmo tempo. Threads são utilizadas principalmente para criar aplicações concorrentes, como servidores, jogos, sistemas distribuídos, e aplicações com várias tarefas independentes que precisam ser realizadas simultaneamente. No entanto, o uso inadequado pode levar a problemas como condição de corrida, deadlocks e consumo excessivo de CPU.

Programação concorrente x Programação paralela

Ante de se aprofundar nas threads é necessário entender a diferença entre programação concorrente e programação paralela. A principal diferença entre elas é a sua forma de execução, enquanto na concorrente o programa é executado sequencialmente concorrendo pela disponibilidade dos processadores com os demais programas. Cada processador executa apenas uma linha de código por vez. A maior desvantagem desse tipo de programação é que qualquer ação que demanda algum tempo de processamento irá ser executada até o fim e nenhuma outra ação poderá ser executada ao mesmo tempo, "travando o usuário".

Já na programação paralela, também chamada de assíncrona, são usadas mais linhas de execuções, onde o programa é divido em vários processos, chamados de Threads, que são executados paralelamente com o processo principal. Dessa forma a principal desvantagem da concorrente é evitada aqui. Mas como desvantagem pode ter um custo alto de processamento dependendo da quantidade de threads utilizadas comparadas a capacidade do processador.

Criando uma Thread em Java

No Java, há diferentes maneiras de criar e gerenciar threads. A abordagem mais comum envolve o uso da classe Thread ou da interface Runnable. O mais recomendado é usar a interface Runnable, pois permite que a classe continue herdando de outra, caso necessário.

Abaixo segue um exemplo de como criar através da interface Runnable



Ao implementar a interface Runnable, é necessário sobrescrever o método run(), dentro desse método é onde será executado o código da thread e o método sleep() faz a thread ser interrompida por um determinado período de tempo, no caso do código acima 1000 milissegundos. Alguns outros métodos bastante utilizados juntamente com as threads são o start(), que cria uma nova thread separada para executar o método dentro do run() e o join(), que quando utilizado interrompe a thread principal até que o código na thread seja executado.

Threads com Spring

No spring existem várias maneiras de se trabalhar com threads, se aproveitando das abstrações do framework. Vamos falar sobre algumas delas.

1- Usando @Async

Através dessa anotação o spring permite executar métodos em uma thread separa sem precisar gerenciá-las manualmente. Primeiramente é necessário habilitar o @Async na aplicação através de uma classe de configuração como no exemplo abaixo. Onde é possível definir a quantidade de threads, quantas podem aguardar na fila e até o nome das threads.



Após isso, nos métodos que forem ser executados pela thread devem ser anotados com @Async. 




2- Agendando tarefas com @Scheduled

Através dessa anotação, o spring permite executar tarefas em intervalos de tempo pré-definidos. Para habilitar, também é necessário criar uma classe de configuração como no exemplo abaixo.


Após isso basta anotar os métodos com @Scheduled para funcionar. No exemplo abaixo executando a cada 5 minutos



Possíveis problemas

Apesar de serem bastante vantajosas em determinadas situações é necessário se atentar a alguns pontos na hora de implementar threads. Alguns possíveis problemas podem ocorrer quando threads diferentes executam o mesmo método, podendo causar comportamentos imprevisíveis. Por exemplo, você tem um método que faz um saque de uma conta, mas se duas threads acessarem ao mesmo tempo, pode ser que o valor retirado da conta seja incorreto.

Outro problema que pode ocorrer são os deadlocks (bloqueios mutualmente excludentes) que ocorrem quando duas ou mais threads ficam presas esperando indefinidamente por recursos bloqueados por outras threads. Também tenha cuidado com o tipo de dado que pode ser manipulado pelas threads, alguns dados são thread-safe, ou seja podem ser acessados e modificados por múltiplas threads sem causar problemas de concorrência. Alguns exemplos são as coleções concorrentes (ConcurrentHashMap), além das classes imutáveis (String, Integer, Long, etc.) e StringBuffer.

Conclusão

Threads são uma ferramenta poderosa para melhorar o desempenho de aplicações Java, permitindo a execução simultânea de múltiplas tarefas. No entanto, se não forem gerenciadas corretamente, podem introduzir problemas complexos, como condições de corrida, deadlocks e vazamento de memória. O Spring facilita o uso de threads com anotações como @Async e @Scheduled, além de permitir a configuração de executores personalizados para otimizar o desempenho. 

Quando for fazer a implementação da thread faça algumas perguntas para entender se esse seria a implementação ideal para esse caso:

- A concorrência é realmente necessária?

- Os dados compartilhados estão protegidos corretamente?

- A aplicação pode lidar com exceções em threads assíncronas?

Se bem utilizadas, as threads podem tornar sua aplicação mais eficiente e responsiva. Mas se forem mal gerenciadas, podem criar mais problemas do que soluções.


 




Comments

Popular posts from this blog

Spring VS Micronaut VS Quarkus

Spring + Redis: Como melhorar sua aplicação com caching

Java + Kafka, Como essa parceria fucniona ?