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

Programação simultânea significa trabalhar de forma mais inteligente, não trabalhar mais

Complementando a coleção de simultaneidade fácil e simples, o java.util.concurrent introduziu outros componentes integrados que ajudam a regular e executar encadeamentos em aplicativos multiencadeados. Ted Neward introduz mais cinco itens indispensáveis de programação Java™ do pacote dojava.util.concurrent .

Ted Neward, Principal, Neward & Associates

Ted Neward photoTed 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.



17/Mai/2011

As coleções simultâneas facilitam a programação simultânea fornecendo estruturas de dados thread-safe bem ajustadas. No entanto, em alguns casos os desenvolvedores precisam ir além e pensar sobre regulação e/ou regulação da execução de encadeamento. Visto que a intenção principal do java.util.concurrent é simplificar a programação multiencadeada, é esperado que o pacote inclua utilitários de sincronização — e ele inclui.

Este artigo, uma continuação da Parte 1, introduz diversas construções de sincronização com um nível mais alto do que as linguagens principais primitivas (monitores), mas não tão alto de forma que são integradas em uma classe de coleção. O uso desses bloqueios e barreiras é muito simples quando se conhece seus objetivos.

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 ajudar você a superar até mesmo os desafios de programação mais difíceis.

1. Semáforo

Em alguns sistemas corporativos, não é incomum os desenvolvedores precisarem regular o número de solicitações abertas (encadeamentos/ações) com relação a um recurso particular — na verdade, a regulação pode ocasionalmente melhorar o rendimento de um sistema reduzindo a quantidade de contenção com relação a esse recurso específico. Mesmo que seja seguramente possível tentar escrever o código regulador a mão, é mais fácil usar a classe de semáforo, que cuida da regulação para você, conforme mostrado na Listagem 1:

Listagem 1. Use o semáforo para regular
import java.util.*;import java.util.concurrent.*;

public class SemApp
{
    public static void main(String[] args)
    {
        Runnable limitedCall = new Runnable() {
            final Random rand = new Random();
            final Semaphore available = new Semaphore(3);
            int count = 0;
            public void run()
            {
                int time = rand.nextInt(15);
                int num = count++;
                
                try
                {
                    available.acquire();
                    
                    System.out.println("Executing " + 
                        "long-running action for " + 
                        time + " seconds... #" + num);
                
                    Thread.sleep(time * 1000);

                    System.out.println("Done with #" + 
                        num + "!");

                    available.release();
                }
                catch (InterruptedException intEx)
                {
                    intEx.printStackTrace();
                }
            }
        };
        
        for (int i=0; i<10; i++)
            new Thread(limitedCall).start();
    }
}

Apesar dos 10 encadeamentos desta amostra estarem em execução (o que pode ser verificado executando o jstack com relação ao processo Java SemApp em execução), apenas três estão ativos. Os outros sete estão suspensos no compartimento até uma das contagens do semáforo ser liberada. (Na verdade, a classe de semáforo oferece suporte para a obtenção e liberação de mais de um permit por vez, mas isso não faria sentido neste cenário.)


2. CountDownLatch

Se o semáforo é a classe de simultaneidade projetada para permitir a entrada de encadeamentos, um de cada vez (lembrando os seguranças das casas noturnas populares), o CountDownLatch seria o portão de largada de uma corrida de cavalos. Essa classe mantém todos os encadeamentos no compartimento até o cumprimento de uma condição específica, quando todos os encadeamentos são liberados juntos.

Listagem 2. CountDownLatch: Que comecem as corridas!
import java.util.*;
import java.util.concurrent.*;

class Race
{
    private Random rand = new Random();
    
    private int distance = rand.nextInt(250);
    private CountDownLatch start;
    private CountDownLatch finish;
    
    private List<String> horses = new ArrayList<String>();
    
    public Race(String... names)
    {
        this.horses.addAll(Arrays.asList(names));
    }
    
    public void run()
        throws InterruptedException
    {
        System.out.println("And the horses are stepping up to the gate...");
        final CountDownLatch start = new CountDownLatch(1);
        final CountDownLatch finish = new CountDownLatch(horses.size());
        final List<String> places = 
            Collections.synchronizedList(new ArrayList<String>());
        
        for (final String h : horses)
        {
            new Thread(new Runnable() {
                public void run() {
                    try
                    {
                        System.out.println(h + 
                            " stepping up to the gate...");
                        start.await();
                        
                        int traveled = 0;
                        while (traveled < distance)
                        {
                            // In a 0-2 second period of time....
                            Thread.sleep(rand.nextInt(3) * 1000);
                            
                            // ... a horse travels 0-14 lengths
                            traveled += rand.nextInt(15);
                            System.out.println(h + 
                                " advanced to " + traveled + "!");
                        }
                        finish.countDown();
                        System.out.println(h + 
                            " crossed the finish!");
                        places.add(h);
                    }
                    catch (InterruptedException intEx)
                    {
                        System.out.println("ABORTING RACE!!!");
                        intEx.printStackTrace();
                    }
                }
            }).start();
        }

        System.out.println("And... they're off!");
        start.countDown();        

        finish.await();
        System.out.println("And we have our winners!");
        System.out.println(places.get(0) + " took the gold...");
        System.out.println(places.get(1) + " got the silver...");
        System.out.println("and " + places.get(2) + " took home the bronze.");
    }
}

public class CDLApp
{
    public static void main(String[] args)
        throws InterruptedException, java.io.IOException
    {
        System.out.println("Prepping...");
        
        Race r = new Race(
            "Beverly Takes a Bath",
            "RockerHorse",
            "Phineas",
            "Ferb",
            "Tin Cup",
            "I'm Faster Than a Monkey",
            "Glue Factory Reject"
            );
        
        System.out.println("It's a race of " + r.getDistance() + " lengths");
        
        System.out.println("Press Enter to run the race....");
        System.in.read();
        
        r.run();
    }
}

Observe na Listagem 2 que o CountDownLatch tem duas funções: Primeiro ele libera todos os encadeamentos simultaneamente, simulando o início de uma corrida; mas posteriormente uma trava diferente simula o fim da corrida, basicamente para que o encadeamento "principal" possa imprimir os resultados. Para uma corrida com mais comentários, é possível incluir os CountDownLatches em pontos específicos e na metade do caminho da corrida, à medida que os cavalos cruzam os valores de um quarto, da metade e de três quartos da distância.


3. Executor

Os exemplos na Listagem 1 e na Listagem 2 possuem uma falha bastante frustrante, pois forçam a criação de objetos Thread diretamente. Esta é uma fórmula para problemas, pois em algumas JVMs, a criação de um encadeamento é uma operação complicada, sendo muito mais fácil a reutilização dos encadeamentos existentes do que a criação de novos encadeamentos. No entanto, em outras JVMs, ocorre exatamente o inverso: os encadeamentos são muito leves e é muito mais fácil criar um novo encadeamento quando necessário. É óbvio que Murphy estará presente (ele sempre está), sendo assim, independente da abordagem usada, será sempre a abordagem errada para a plataforma que está sendo implementada.

O Grupo de Especialistas em JSR-166 (consulte a seção Recursos) antecipou essa situação de alguma forma. Ao invés dos desenvolvedores Java criarem encadeamentos diretamente, eles introduziram a interface Executor , uma abstração para a criação de novos encadeamentos. Conforme mostrado na Listagem 3, o Executor permite a criação de encadeamentos sem a necessidade de criação de novos objetos Thread :

Listagem 3. Executor
Executor exec = getAnExecutorFromSomeplace();
exec.execute(new Runnable() { ... });

A principal desvantagem do uso do Executor é a mesma encontrada com todas as factories: o factory tem que vir de algum lugar. Infelizmente, diferente do CLR, a JVM não vem com um conjunto de encadeamentos abrangente de VM padrão.

A classe Executoratua com um local comum de obtenção de instâncias de implementação do Executor, mas ela possui somente novos métodos (para criar um novo conjunto de encadeamentos, por exemplo); ela não possui instâncias pré-criadas. Então você estará por sua conta se quiser criar e usar instâncias do Executor no seu código. (Ou, em alguns casos, será possível usar uma instância fornecida pelo contêiner/plataforma escolhido.)

ExecutorService, ao seu dispor

Por mais que seja útil não ter que se preocupar com a origem dos encadeamentos, a interface Executor carece de algumas funcionalidades esperadas pelo desenvolvedor Java, como a capacidade de acionar um encadeamento projetado para produzir um resultado e esperar, sem bloqueios, que esse resultado fique disponível. (Isso é uma necessidade muito comum em aplicativos de desktop, nos quais o usuário executa uma operação de UI que requer o acesso a um banco de dados e deseja cancelar a operação antes da sua conclusão se ela demorar muito.)

Para isso, os especialistas em JSR-166 criaram uma abstração muito mais útil, a interface do ExecutorService , que modela o factory iniciador do encadeamento como um serviço que pode ser controlado coletivamente. Por exemplo: ao invés de chamar o execute() uma vez para cada tarefa, o ExecutorService pode obter uma coleção de tarefas e retornar uma Lista de Futuros representando os resultados futuros de cada uma das tarefas.


4. ScheduledExecutorServices

Por melhor que a interface ExecutorService seja, certas tarefas precisam ser executadas de maneira planejada, como a execução de uma determinada tarefa em intervalos determinados ou em um horário específico. Essa é a atribuição do ScheduledExecutorService, que estende o ExecutorService.

Se o seu objetivo for criar um comando de "pulsação" que executava um "ping" a cada cinco segundos, o ScheduledExecutorService tornará essa tarefa simples, conforme visto na Listagem 4:

Listagem 4. 'Pings' do ScheduledExecutorService planejados
import java.util.concurrent.*;

public class Ping
{
    public static void main(String[] args)
    {
        ScheduledExecutorService ses =
            Executors.newScheduledThreadPool(1);
        Runnable pinger = new Runnable() {
            public void run() {
                System.out.println("PING!");
            }
        };
        ses.scheduleAtFixedRate(pinger, 5, 5, TimeUnit.SECONDS);
    }
}

Que tal? Sem confusão com os encadeamentos, sem confusão com o que fazer se o usuário quiser cancelar a pulsação, sem a marcação explícita de encadeamentos no primeiro plano ou no segundo plano; simplesmente deixe todos esses detalhes de planejamento para o ScheduledExecutorService.

Casualmente, seu um usuário quiser cancelar a pulsação, o retorno da chamada scheduleAtFixedRate seria uma instância do ScheduledFuture , que não somente envolve o resultado, se houver algum, mas também possui um método cancel para encerrar a operação planejada.


5. Métodos de expiração

A capacidade de colocação de um tempo de espera concreto nas operações de bloqueio (evitando dessa forma os conflitos) é um dos excelentes avanços da biblioteca do java.util.concurrent em relação aos seus parentes antigos de simultaneidade, como os monitores para bloqueio.

Esses métodos na maioria das vezes são sobrecarregados com um par int/TimeUnit , indicando quanto tempo o método deve esperar antes de serem liberados e retornarem o controle para o programa. Isso requer mais trabalho por parte do desenvolvedor — como recuperar se o bloqueio não for obtido? — mas na maioria das vezes os resultados são mais corretos: menos conflitos e mais códigos de segurança de produção. (Para obter mais informações sobre a composição de códigos prontos para produção, consulte o artigo Release It! de Michael Nygard na seção Recursos.)


Conclusão

O pacote java.util.concurrent contém muitos utilitários inteligentes que vão além das coleções, principalmente nos pacotes .locks e .atomic . Procure e você também localizará estruturas de controle úteis como o CyclicBarrier , entre outras.

Como ocorre em muitos aspectos da plataforma Java, não é necessário procurar muito para localizar códigos de infraestrutura que podem ser muito úteis. Sempre que estiver compondo códigos multiencadeados, lembre-se dos utilitários discutidos neste e no artigo anterior.

Da próxima vez, iremos explorar um novo tópico: cinco coisas que você não sabia sobre Jars.


Download

DescriçãoNomeTamanho
Sample code for this article5things5-src.zip10KB

Recursos

Aprender

Discutir

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

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

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

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

Elija su nombre para mostrar



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.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

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

 


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


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