Dev (Back & Front)ARTIGO

Filosofia de aplicações concorrentes

Ao longo das experiências que venho tendo com as consultorias, treinamentos, equipes de desenvolvimento, certificações etc., percebo que, no geral, a galera apresenta uma grande dificuldade com o tratamento de questões relacionadas à Thread. Após algumas conversas e entrevistas, diagnostiquei que o fato acontece não por causa da complexidade da API ou dificuldades de sintaxe, mas por falta de uma efetivo entendimento da FILOSOFIA sobre a questão. Eu peguei uma série de casos aonde o pessoal (no geral) não conseguia dominar ou entender sintaxe/API justamente pelo fato de não ter uma clara instrução do propósito e fundamento da mesma. Com isso, resolvi criar este post com o intuito de cobrir esse buraco e tentar esclarecer o assunto.

O objetivo de toda equipe de desenvolvimento é produzir programas que executem uma série de tarefas logicamente organizadas. Na maioria dos casos é isso mesmo, simples assim….um sistema programado para executar um monte de tarefas e seqüências cronologicamente estipuladas. Por uma outra perspectiva, o sistema somente executará “uma tarefa por vez”. A questão surge quando é identificado que em algumas partes, de alguns programas, existem certos processos que não necessitam ser executados seqüencialmente, ou seja, não faz sentido para aquele escopo em especial que uma tarefa espere uma outra ser terminada para que a mesma possa ser executada. Podemos pegar o exemplo clássico de um editor de texto – não faz sentido que o usuário deva esperar o arquivo de 100 páginas ser impresso para depois salvá-lo. Na verdade, o programa deveria permitir que o usuário pudesse fazer estas duas tarefas ao mesmo tempo. Com isso, vemos que podemos ter 2 tipos de aplicação:

  • Monothread – que executa tarefas sequenciais.
  • Multithread – que executa tarefas paralelas, ao mesmo tempo e concorrentemente.

É por isso que dizemos que a tecnologia java é multithread

….isso quer dizer que com ela podemos criar programas que executem inúmeras tarefas concorrentemente. O autor do programa java está livre para criar e delimitar quantos processos seqüências e concorrentes poderão acontecer dentro do sistema. Esta mágica acontece simplesmente com a utilização da interface Runnable e a classe Thread. Que legal, que lindo..maravilhoso? Não..nada de lindo e maravilhoso!! A possibilidade de fazer aplicações multithread resulta em uma situação onde muitos problemas podem surgir. Vejamos os comuns:

Acesso Concorrente

Se pararmos e pensarmos que um programa pode executar inúmeras tarefas ao mesmo tempo, chegaremos à conclusão que poderá acontecer situações onde dois ou mais processos poderão manipular os mesmos recursos causando o problema chamado de acesso concorrente. Eu sempre costumo dizer que a POO nada mais é que o reflexo das coisas que acontecem no mundo real e isso não é diferente para a questão do acesso concorrente. Imagine a situação onde um pai tente gerenciar que os seus dois filhos brinquem com seu brinquedos. Do ponto de vista funcional, a coisa é simples, ou o pai bloqueia uma criança enquanto a outra brinca com o brinquedo, ou ele compra um brinquedo para cada criança, fazendo com que elas possam brincar ao mesmo tempo.

Por causa desta situação é que encontramos o controle de bloqueio e o modificador de acesso chamado synchronized na tecnologia java. Com ele, o desenvolvedor tem a capacidade de bloquear um objeto, impedindo que o mesmo seja usando por um outro(s) processo(s) que porventura tente acessá-lo. Veja, o oposto é verdadeiro: a ausência do operador faz que com o objeto possa ser utilizado concorrentemente, ficando, então, a cargo do desenvolvedor identificar os casos e decidir o que fazer. Um caso clássico de exemplo apresentado pela maioria dos livros seriam duas pessoas tentando sacar dinheiro de uma mesma conta bancária. Caso os objetos responsáveis pelas operações não sejam corretamente bloqueados, poderão finalizar em estado inconsistente.

Gerenciamento de Threads

Estamos nesta conversa toda justamente pelo fato de alguns processos não precisarem ser executados seqüencialmente, concluindo que a programação multithread foi concebida para deixar os sistemas mais rápidos e interativos. A coordenação das threads acontece em um terceiro cenário, onde um sistema multithread apresenta casos em que determinados processos concorrentes precisam ser controlados minuciosamente, apresentando uma certa interdependência entre eles. Imagine uma situação onde cinco crianças querem comprar sorvete, tendo apenas um atendente para todas elas. Vamos usar este simples exemplo e levantar algumas variações das situações:

  1. A criança que for selecionada pelo atendente terá que bloqueá-lo, fazendo com que ele não atenda uma outra criança em paralelo. A solução para este exemplo de controle de acesso já foi mencionada anteriormente utilizando o operador synchronized.
  2. Se caso uma criança selecionada para ser atendida ainda estiver em dúvida qual sabor da casquinha escolher, o oposto terá que acontecer, ou seja, o sorveteiro terá que ser liberado para que possa escolher outra criança para atender, enquanto aquela que teve a oportunidade fica em um tempo de espera indeterminado até que tenha uma nova oportunidade.
  3. Caso uma criança selecionada para ser atendida fale que quer o mesmo sabor que seu irmãozinho mais velho, o sorveteiro terá que ser liberado para que possa escolher uma outra criança e somente voltar nesta depois do seu irmão for atendido.
  4. Se uma criança selecionada para ser atendida falar que gostaria de passar a vez para sua irmãzinha mais nova que está morrendo de vontade de tomar sorvete, o sorveteiro terá que ser liberado e forçado a atender uma determinada criança em específico.

Se no mundo real acontece assim, em java não será diferente! Uma equipe de desenvolvimento pode se deparar com um escopo de projeto multithread que contenha algum(s) caso(s) de gerenciamento de thread, e a tecnologia contém todos os artifícios necessários para que os desenvolvedores possam implementar este controle. Para os casos 2, 3, e 4 acima descritos, vemos que os métodos herdados da classe Object: wait(), notify() e notifyAll() são utilizados para implementar este controle sobre as instâncias do objetos que estão sendo executados dentro das threads. Juntamente com eles, podemos destacar os métodos das classe classe Thread: join(), resume() isAlive(), yie(), interrupted(), setDaemon() e sleep() que também são usados para dar a possibilidade de os desenvolvedores implementarem o controle das threads dentro deste ambiente gerenciável.

Outros casos mais “cabeludos” que eu deixarei de fora para não aumentar o artigo seriam o DeadLock, Race Conditions e o Starvation. Veja os link para mais detalhes.

Bom, é isso, galera, o artigo não tem o objetivo de descrever cada caso ou detalhamentos sobre as API da tecnologia java para ambientes multithread, ficando a cargo de cada um se aprofundar no assunto. Para os interessados, seguem dois livros que podem fornecer o conteúdo necessário – Certificação Sun para Programador Java 5 – Inglês e Português e SCJD Exam with J2SE 5 Inglês.

O artigo fica aberto para opiniões e complementos. Até a próxima =D

É graduado e pós-graduado em Engenharia de Software, há mais de 15 anos desempenhando atividades de gestão, consultoria e treinamentos no desenvolvimento de soluções corporativas desktop, web e mobile com a plataforma Java e metodologias Ágeis. Possui mais de 30 certificações em Java, engenharia de software e metodologias ágeis.

Ver perfil