Avançar para a área de conteúdo

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

A primeira vez que acessar o developerWorks, um perfil será criado para você. Informações do seu perfil (tais como: nome, país / região, e empresa) estarão disponíveis ao público, que poderá acompanhar qualquer conteúdo que você publicar. Seu perfil no developerWorks pode ser atualizado a qualquer momento.

Todas as informações enviadas são seguras.

  • Fechar [x]

Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

Todas as informações enviadas são seguras.

  • Fechar [x]

Cinco coisas que você não sabia sobre... java.util.concurrent, Parte 1

Programação multiencadeada com coleções simultâneas

Ted Neward, Principal, Neward & Associates
Ted Neward photo
Ted Neward é o diretor da Neward & Associates, onde ele dá consultoria, orientação, ensina e faz apresentações sobre Java, .NET, Serviços XML e outras plataformas. Ele reside perto de Seattle, Washington.

Resumo:  A composição de códigos multiencadeados que executam corretamente e protegem aplicativos contra danos é muito difícil — é por isso que existe o java.util.concurrent. Ted Neward mostra como as classes de coleções simultâneas como CopyOnWriteArrayList, BlockingQueuee ConcurrentMap aperfeiçoam as classes de coleções padrão para suas necessidades de programação simultânea.

Data:  17/Mai/2011
Nível:  Introdutório Também disponível em :   Inglês
Atividade:  1891 visualizações
Comentários:  


Sobre esta série

Então, você acha que possui conhecimento sobre programação Java? A verdade é que a maioria dos desenvolvedores tem apenas algum conhecimento sobre a plataforma Java, aprendendo o suficiente para concluir a tarefa. Nesta série, Ted Neward examina a fundo a funcionalidade da plataforma Java para revelar fatos pouco conhecidos que podem lhe ajudar a superar até mesmo os desafios de programação mais difíceis.

As coleções simultâneas foram uma grande inclusão no Java™ 5, mas muitos desenvolvedores Java ignoraram essa inclusão devido à grande propaganda sobre anotações e genéricos. Além disso, e para falar a verdade, muitos desenvolvedores evitam esse pacote porque acreditam que ele, assim como os problemas que ele pretende resolver, é complicado.

Na verdade, o java.util.concurrent contém muitas classes que resolvem de forma efetiva problemas de simultaneidade comuns, sem a necessidade de um trabalho árduo. Leia sobre como as classes java.util.concurrent como CopyOnWriteArrayList e BlockingQueue ajudam a resolver os desafios prejudiciais de programação multiencadeada.

1. TimeUnit

Apesar de não ser uma classe de coleções por assim dizer, a enumeração java.util.concurrent.TimeUnit facilita muito a leitura do código. O uso do TimeUnit livra os desenvolvedores que usam método ou API do controle do milissegundo.

O TimeUnit incorpora todas as unidades de tempo, desde de MILISSEGUNDOS e MICROSSEGUNDOS até DIAS e HORAS, o que significa que ele manipula praticamente todos os tipos de intervalos de tempo necessários para o desenvolvedor. E, graças aos métodos de conversão declarados na enumeração, é ainda mais simples converter HORAS em MILISSEGUNDOS quando o tempo acelera.


2. CopyOnWriteArrayList

A execução de uma nova cópia de um array é uma operação muito cara, em termos de tempo e sobrecarga de memória, para ser usada normalmente, muitas vezes os desenvolvedores preferem usar um ArrayList sincronizado. No entanto, essa também é uma opção cara, pois todas as vezes que é repetida nos conteúdos de uma coleção é necessário sincronizar todas as operações, incluindo leitura e gravação, para garantir a consistência.

Isso retrocede a estrutura de custo em cenários nos quais diversos leitores leem o ArrayList , mas poucos o modificam.

CopyOnWriteArrayList é o que resolve esse problema. Seu Javadoc define CopyOnWriteArrayList como uma "variante thread-safe do ArrayList na qual todas as operações variáveis (add, set entre outras) são implementadas através da criação de uma nova cópia do array".

A coleção copia internamente o seu conteúdo para um novo array em caso de modificação, dessa forma os leitores que acessam os conteúdos do array não ficam sujeitos aos custos de sincronização (pois não estão operando em dados variáveis).

Basicamente, o CopyOnWriteArrayList é ideal para o cenário no qual o ArrayList falha: leitura frequente e gravação rara, como os ouvintesde um evento JavaBean.


3. BlockingQueue

A interface BlockingQueue declara ser uma Fila, o que significa que seus itens são armazenados na ordem first in, first out (FIFO). Os itens inseridos em uma ordem específica são recuperados na mesma ordem — mas com a inclusão da garantia de que qualquer tentativa de recuperação de um item de uma fila vazia irá bloquear o encadeamento de chamada até que o item esteja pronto para ser recuperado. Da mesma forma, qualquer tentativa de inserção de um item em uma fila cheia irá bloquear o encadeamento de chamada até que haja espaço disponível no armazenamento da fila.

O BlockingQueue resolve perfeitamente o problema de como "transferir" itens reunidos por um encadeamento para outro encadeamento para processamento, sem a preocupação com problemas de sincronização. A trilha de blocos protegidos no tutorial do Java é um bom exemplo. Ela desenvolve um buffer delimitado com um único intervalo usando sincronização manual e wait()/notifyAll() para sinalizar entre os encadeamentos quando um novo item está disponível para consumo e quando o intervalo está pronto para ser preenchido com um novo item. (Consulte a seção de implementação de blocos protegidos para obter mais detalhes.)

Apesar do código no tutorial de blocos protegidos funcionar, ele é longo, complicado e não é totalmente intuitivo. No começo da plataforma Java, os desenvolvedores realmente tinham que lidar com tais códigos, mas em 2010— as coisas certamente melhoraram, não?

A Listagem 1 mostra uma versão reescrita do código dos blocos protegidos na qual foi empregado o ArrayBlockingQueue ao invés da versão manual Drop.


Listagem 1. BlockingQueue
import java.util.*;
import java.util.concurrent.*;

class Producer
    implements Runnable
{
    private BlockingQueue<String> drop;
    List<String> messages = Arrays.asList(
        "Mares eat oats",
        "Does eat oats",
        "Little lambs eat ivy",
        "Wouldn't you eat ivy too?");
        
    public Producer(BlockingQueue<String> d) { this.drop = d; }
    
    public void run()
    {
        try
        {
            for (String s : messages)
                drop.put(s);
            drop.put("DONE");
        }
        catch (InterruptedException intEx)
        {
            System.out.println("Interrupted! " + 
                "Last one out, turn out the lights!");
        }
    }    
}

class Consumer
    implements Runnable
{
    private BlockingQueue<String> drop;
    public Consumer(BlockingQueue<String> d) { this.drop = d; }
    
    public void run()
    {
        try
        {
            String msg = null;
            while (!((msg = drop.take()).equals("DONE")))
                System.out.println(msg);
        }
        catch (InterruptedException intEx)
        {
            System.out.println("Interrupted! " + 
                "Last one out, turn out the lights!");
        }
    }
}

public class ABQApp
{
    public static void main(String[] args)
    {
        BlockingQueue<String> drop = new ArrayBlockingQueue(1, true);
        (new Thread(new Producer(drop))).start();
        (new Thread(new Consumer(drop))).start();
    }
}

O ArrayBlockingQueue também cumpre a "integridade" — o que significa que ele pode conceder aos encadeamentos de leitura e gravação o acesso primeiro a entrar, primeiro a sair. A alternativa seria uma política mais eficiente que poderia deixar alguns encadeamentos sem processamento. (Ou seja, seria mais eficiente permitir a execução de leitores enquanto outros leitores permanecem bloqueados, mas isso poderia gerar um fluxo constante de encadeamentos de leitores o que impediria os gravadores de executarem sua tarefa.)

Vigilância de erro!

A propósito, você estava certo se observou que os blocos protegidos contêm um grande erro — o que aconteceria se um desenvolvedor sincronizasse a instância Drop dentro de main()?

O BlockingQueue também oferece suporte para métodos que usam parâmetros de tempo, indicando quanto tempo o segmento deve permanecer bloqueado antes de retornar a falha de sinal para inserir ou recuperar o item em questão. Isso evita uma espera ilimitada, o que poderia ser fatal para um sistema de produção, visto que uma espera ilimitada pode se tornar facilmente uma queda do sistema que requer a reinicialização.


4. ConcurrentMap

O map possui um erro de simultaneidade sutil que já prejudicou muitos desenvolvedores Java desatentos. ConcurrentMap é a solução simples.

Quando um Map é acessado de diversos encadeamentos, é comum usar containsKey() ou get() para descobrir se uma determinada chave está presente antes de armazenar o par chave/valor. Mas mesmo com um Mapsincronizado, um encadeamento poderia penetrar durante esse processo e confiscar o controle do Map. O problema é que o bloqueio é obtido no começo de get()e liberado antes do bloqueio poder ser adquirido novamente, no chamado para put(). O resultado é uma disputa entre dois encadeamentos e o resultado será diferente dependendo do encadeamento que for executado primeiro.

Se dois encadeamentos chamarem um método ao mesmo tempo, ambos irão testar e efetuar a inclusão, desperdiçando o valor do primeiro encadeamento no processo. Felizmente, a interface ConcurrentMap oferece suporte para diversos métodos adicionais projetados para executar duas tarefas com um único bloqueio: putIfAbsent(), por exemplo, executa o teste primeiro e, em seguida, executa a inclusão somente se a chave não estiver armazenada em Map.


5. SynchronousQueues

SynchronousQueue é um item interessante, de acordo com o Javadoc:

Uma fila de bloqueio na qual cada operação de inserção deve aguardar uma operação de remoção correspondente por outro encadeamento, e vice-versa. Uma fila síncrona não possui nenhuma capacidade interna, nem mesmo a capacidade para um item.

O SynchronousQueue é basicamente outra implementação do supramencionado BlockingQueue. Ele fornece uma maneira leve de trocar elementos únicos de um encadeamento para outro, usando a semântica de bloqueio usada por ArrayBlockingQueue. Na Listagem 2, eu reescrevi o código da Listagem 1 usando SynchronousQueue ao invés de ArrayBlockingQueue:


Listagem 2. SynchronousQueue
import java.util.*;
import java.util.concurrent.*;

class Producer
    implements Runnable
{
    private BlockingQueue<String> drop;
    List<String> messages = Arrays.asList(
        "Mares eat oats",
        "Does eat oats",
        "Little lambs eat ivy",
        "Wouldn't you eat ivy too?");
        
    public Producer(BlockingQueue<String> d) { this.drop = d; }
    
    public void run()
    {
        try
        {
            for (String s : messages)
                drop.put(s);
            drop.put("DONE");
        }
        catch (InterruptedException intEx)
        {
            System.out.println("Interrupted! " + 
                "Last one out, turn out the lights!");
        }
    }    
}

class Consumer
    implements Runnable
{
    private BlockingQueue<String> drop;
    public Consumer(BlockingQueue<String> d) { this.drop = d; }
    
    public void run()
    {
        try
        {
            String msg = null;
            while (!((msg = drop.take()).equals("DONE")))
                System.out.println(msg);
        }
        catch (InterruptedException intEx)
        {
            System.out.println("Interrupted! " + 
                "Last one out, turn out the lights!");
        }
    }
}

public class SynQApp
{
    public static void main(String[] args)
    {
        BlockingQueue<String> drop = new SynchronousQueue<String>();
        (new Thread(new Producer(drop))).start();
        (new Thread(new Consumer(drop))).start();
    }
}

O código de implementação parece quase igual, mas o aplicativo inclui um benefício, o SynchronousQueue permitirá uma inserção na fila somente se houver um encadeamento aguardando para utilizá-lo.

Na prática, o SynchronousQueue é similar aos "canais rendezvous" disponíveis em linguagens como Ada ou CSP. Em outros ambientes, eles também são conhecidos como "junções" incluindo o .NET (consulte a seção Recursos).


Conclusão

Por que brigar com a introdução de simultaneidade em suas classes de coleção quando a biblioteca do Java Runtime oferece equivalentes úteis pré-desenvolvidos? O próximo artigo desta séria explora ainda mais o namespace java.util.concurrent .



Download

DescriçãoNomeTamanhoMétodo de download
Sample code for this articlej-5things4-src.zip23KBHTTP

Informações sobre métodos de download


Recursos

Aprender

Discutir

Sobre o autor

Ted Neward photo

Ted Neward é o diretor da Neward & Associates, onde ele dá consultoria, orientação, ensina e faz apresentações sobre Java, .NET, Serviços XML e outras plataformas. Ele reside perto de Seattle, Washington.

Ajuda para Relatar Abuso

Relatar abuso

Obrigado. Esta entrada foi sinalizada para atenção do moderador.


Ajuda para Relatar Abuso

Relatar abuso

Falha no envio do Relatório de abuso. Tente novamente mais tarde.


developerWorks: Registre-se


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos de uso do developerWorks.

 


Na primeira vez que você efetua sign in no developerWorks, um perfil é criado para você. Informações selecionadas do seu perfil developerWorks são exibidas ao público, mas você pode editá-las a qualquer momento. Seu primeiro nome, sobrenome (a menos que escolha ocultá-los), e seu nome de exibição acompanharão o conteúdo que postar.

Selecione seu nome de exibição

Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

(Deve possuir de 3 a 31 caracteres.)


Ao clicar em Enviar, você concorda com os termos de uso do developerWorks.

 


Classificar este artigo

Comentários

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Tecnologia Java
ArticleID=657891
ArticleTitle=Cinco coisas que você não sabia sobre... java.util.concurrent, Parte 1
publish-date=05172011
author1-email=ted@tedneward.com
author1-email-cc=