Artigo Java Magazine 69 - Aplicações concorrentes em Java
Artigo da Revista Java Magazine Edição 69.
[Artigo já está disponível no Leitor Digital DevMedia®. Clique aqui para acessá-lo]
> Clique aqui para ler todos os artigos da Java Magazine 69
Aplicações concorrentes em Java
Desenvolvendo aplicações concorrentes estáveis e escaláveis
Implemente códigos com maior facilidade, em menos tempo, mais confiáveis, escaláveis e de manutenção simples, com a nova API Concurrent
De que se trata o artigo:
Desenvolvimento de software com processamento concorrente usando a API java.util.concurrent. E a comparação entre o esforço utilizado para desenvolver um software concorrente utilizando as primitivas de concorrência e utilizando a API.
Para que serve:
Aumentar a qualidade, o desempenho e a escalabilidade de software com processamento concorrente. As classes da API são bem testadas e confiáveis, e tornam o código mais compreensivo, facilitando atividades de manutenção.
Em que situação o tema é útil:
Com a evolução dos computadores de múltiplos núcleos, e a crescente demanda por processamento, desenvolver softwares concorrentes com eficiência, facilidade, escalabilidade e boa manutenibilidade são alguns dos objetivos mais comuns para empresas de desenvolvimento de software.
Aplicações concorrentes em Java:
O Java, assim como outras linguagens, escolheu o modelo de threads para tratar tarefas que podem ser executadas simultaneamente. Esse modelo tem suas vantagens e desvantagens (assim como qualquer modelo). O Java sempre trouxe a concorrência como um recurso da linguagem, não algo externo. Mesmo assim, desenvolver aplicações concorrentes sempre foi uma tarefa muito complexa. Com a introdução da java.util.concurrent, a complexidade para desenvolver tais aplicações diminuiu muito. Utilizando essa ferramenta, mesmo programadores menos experientes podem escrever aplicações concorrentes com qualidade e segurança, obtendo ótimos resultados.
A evolução dos processadores com um único núcleo baseada em clocks com maior freqüência atingiu o limite do aquecimento suportado e um custo/beneficio inviável. Esses processadores enfrentavam ainda outros problemas, como alto consumo de energia e gargalo no acesso à memória. Portanto, novas alternativas para a melhoria de desempenho e consumo ganharam destaque, entre elas o paralelismo de threads e cores. Os processadores de múltiplos núcleos (MC) e a tecnologia SMT (Simultaneous Multithreading) permitem que tarefas sejam executadas em paralelo melhorando o aproveitamento dos recursos.
Para tirar-se maior proveito desses avanços, é necessário mudar o modo de pensar e escrever aplicações, substituindo-se o paradigma seqüencial pelo concorrente. Dentre os desafios que esse paradigma apresenta, alguns merecem destaque: identificar o que pode ser executado em paralelo (vamos chamar de thread cada parte do código com essa característica) e a sincronização dos threads.
O que são Threads?
Os sistemas operacionais, em sua maioria, suportam processos, isto é, programas independentes que rodam com algum grau de isolamento. Thread é um recurso que permite múltiplas atividades ocorrendo ao mesmo tempo em um mesmo processo. O Java foi uma das primeiras linguagens, com boa aceitação no mercado, que trouxe as threads como parte da linguagem, não as tratando apenas como um recurso controlado pelo sistema operacional.
As threads assim como os processos, são independentes e concorrentes, tendo variáveis locais e pilha (stack) próprias, mas compartilham a memória e estado do processo em que participam. Dessa forma, elas têm acesso aos mesmos objetos alocados no heap. Apesar de facilitar a troca de dados, deve-se tomar cuidado para garantir que isto não resulte em erros de concorrência (data races).
Todos os programas escritos em Java rodam threads, pelo menos uma (main). A JVM cria outras threads que geralmente não são percebidas, como: Garbage Collection (GC), threads da AWT/Swing; em servidores de aplicação conectores de HTTP, EJB etc.
A API do Java para threads é muito simples. Entretanto, escrever programas que as utilizem com eficiência é um trabalho complexo. Então, porque utilizá-las se são tão complicadas? Algumas das principais razões são:
l Aproveitar as vantagens dos computadores multiprocessados;
l Executar tarefas assíncronas ou em background;
l Tornar a interface com o usuário mais fluida;
l Simplificar a modelagem.
Antes da API java.util.concurrent, trabalhar com threads em Java exigia um grande esforço, pois os recursos disponíveis são muito simples e de baixo nível. No exemplo da (Listagens 1 a 4), apresenta-se a implementação de um problema clássico de produtor e consumidor, utilizando os recursos primitivos de concorrência.
A classe principal ProducerConsumer instancia alguns Producers, que geram dados, e Consumers, que os utilizam. Ambos se comunicam por intermédio da fila customizada (representada na classe Queue), implementada utilizando recursos primitivos do Java como synchronized, além dos algoritmos específicos a este tipo de fila. Por exemplo, o método add() não pode inserir elementos se a fila já estiver “cheia”, neste caso o recurso de bloqueio seguido de retry (ver o loop while(isFull()) com um wait()) garante a integridade; assim, se a fila estiver cheia, a thread-produtor que está tentando inserir ficará em wait() até que alguma thread-consumidor remova um elemento com remove(), que ao final faz um notifyAll(), acordando o produtor para o próximo retry. Todo este código é bastante trabalhoso e complexo, além de limitado, em escalabilidade e outras qualidades.
A JDK 5 agregou melhorias significativas para o desenvolvimento de aplicações concorrentes, incluindo modificações na JVM, novos recursos de baixo nível para sincronização, classes de alto nível com bom desempenho e thread-safe (como thread pools), coleções concorrentes, semáforos, locks e barreiras. Um dos objetivos desse artigo é, auxiliar na compreensão de como essas novas classes podem melhorar a produtividade, desenvolvendo códigos mais escaláveis, seguros e mais simples para dar manutenção.
?Utilizando-se da nova API, pode-se trabalhar com recursos testados e confiáveis. Os problemas enfrentados com a programação concorrente, em sua maioria, são parecidos e, as soluções para os aspectos mais comuns fazem parte da nova API. Dessa forma, pode-se juntá-las como blocos para construir-se a aplicação.
Listagem 1. Produtor/Consumidor com Semáforo
package br.com.jm.concurrent;
public class Queue {
private static final Integer MAX_ITENS = 5000;
private Integer itens = new Integer(0);
private boolean firstThousand = false;
private synchronized boolean isFull() {
"
ATENÇÃO! A exibição deste artigo foi interrompida.
Clique aqui e acesse o Leitor Digital DevMedia para ler este artigo completo.

Space do autor


1
0
