Tunning de Aplicações Web

salon-tuning-bA velocidade da aplicação em executar os processos e apresentar algum resultado está intimamente ligada com a experiência do usuário final, que vai passar horas e horas do seu dia ali na frente do sistema que foi maravilhosamente escrito para ele. Na prática, vejo que os responsáveis pelos sistemas em geral – gerentes, projetistas e programadores, não têm colocado isso como um fator de preocupação nos ciclos de desenvolvimento. O resultado é o mesmo de sempre, em ambiente de testes, homologação e durante algum tempo inicial de produção, o sistema funciona lindo e maravilhoso, mas depois de algum tempo começam as reclamações relacionadas com a lentidão crescente. Seguem abaixo as principais práticas que venho utilizando como sucesso como se fosse uma “receita de bolo”utilizar para resolver esse tipo de situação:

Acesso ao Banco de Dados

Reduzir ao máximo o número de vezes que a solução faz acesso ao banco de dados.

Problema: Muitos programadores têm a mania de ir desenvolvendo classes, componentes e módulos sem antes e/ou durante fazer uma análise organizada de como estas partes do sistema estão fazendo estes acessos. Com isso, vemos como resultado aplicações com baixa performance devido aos vários e desnecessários acessos ao banco “round-trips” que se multiplicam a medida do número de usuários simultaneamente conectados.

Solução: Analisar quantas vezes a aplicação está acessando o banco, o porquê do acesso, e assim tentar uma forma de evitar este acesso. Neste momento, muitos profissionais pecam por não conhecerem recursos básicos de banco de dados e de SQL-ANSI, principalmente pelas propagações de frameworks ORM. Qualquer meio é válido, alguns recursos usados para alcançar isso são o uso de VIEWS, JOIN, SUBQUERYS e o Pattern Store Procedure Facade. Todos os programadores têm que possuir um lema em mente: “O acesso remoto é que mais degrada a performance de uma aplicação e o acesso ao banco de dados é um deles, então eu tenho que fazer de tudo para evitar ou minimizar ao máximo.

Índices Adequados

Criar os índices para as todas as tabelas que sofrem consultas na aplicação.

Problema: Muitos programadores não têm o mínimo de fundamentos de banco de dados, criando bancos sem nenhum índice de busca para as tabelas que sofrem alto número de SELECT + WHERE CAMPO. A questão problemática é que quando uma tabela sem índice sofre um SELECT +WHERE CAMPO, o registro é buscado, na maioria das vezes, da pior e mais demorada forma possível, que é o “seqüencialmente”. A pior notícia é que isso é um problema cumulativo, ou seja, quanto mais registro existente, mais demorada fica a consulta. Já tive experiências de diminuir o tempo de um procedimento de fechamento mensal em 50% do tempo, pelo simples ato de criar os índices nas tabelas usados pelo processo.

Solução: Para cada SELECT que o programa faz, em cada tabela, verifique os campos de busca colocados no WHERE e, assim, crie um índice para cada um deles.

Consultas Gigantescas

Sistemas que permitem o usuário consultar e trazer do banco de dados um alto número de registros.

Problema: Algumas situações comumente ocorrentes em sistemas no modelo desktop, ou mais conhecido como “FAT CLIENT”, não se encaixam em aplicativos web. Um destes casos é quando sistemas permitem aos usuários filtrar e trazer do banco de dados consultas com um alto número de registros complemente desnecessário. Em casos em que o modelo era desktop, isso não acarretava problemas devido à própria natureza da solução. Entretanto em sistemas web, onde recursos de execução são compartilhados e o numero de acesso simultâneos é ilimitado, o sistema estará gastando um alto e precioso numero relevante de memória.

Explicando de forma prática, eu já peguei sistemas na redondezas onde os programadores estavam replicando a arquitetura que continha uma camada de persistência CRUD. A questão problemática era que o framework replicava um método que sempre retornava todos os registros existente. Isto estava sendo propagado para todo o sistema e seus processos relacionados. Se paramos para analisar, mecanismos de busca são disponibilizados ao usuário para que ele tenha autonomia de buscar registros individuais ou grupos lógicos deles. Qual seria o motivo, ou o que poderia fazer um usuário com 500 linhas de resultados de uma consulta? Que ser humano na face da terra gostaria de visualizar 500 registros em uma olhada? Mesmo que a regra de negócio ainda apoiasse o caso, o usuário final, sendo um humano comum, não teria tamanha visibilidade para isso.

Solução: Os processos do sistema em questão devem ser analisados e devem ser implementadas validações lógicas corretas, que não permitam  executar consultas que retornem uma alto número de registros no banco de dados. Duas práticas neste tópico são bem comuns – o uso sistemático de paginação, e a implementação de filtros inteligentes, baseados em regra do próprio negócio, que não deixassem a ocorrência de grandes intervalos. Como tudo na vida, existem exceções, e podemos, sim, encontrar situações em sistemas que teriam a necessidade de consultar grandes volumes de registros, mas isso já está mais que comprovado que é uma porcentagem pequena e restrita do total da automação.

Ordenação de Dados

Ordenar dados em memória usando java ao invés de usar ORDER BY.

Problema: Uma funcionalidade muito comum é disponibilizar a ordenação das tabelas apresentadas na aplicação pelas suas próprias colunas. A questão problemática é quando a aplicação efetua mais um acesso ao banco a cada ordenação requisitada. Ou seja, o programador usa o recurso de SELECT ORDER BY para fazer a ordenação.

Solução: Transformar as linhas da tabela em objetos java e, assim, ordená-los usando recursos do JSE, evitando gasto com tempo, acesso ao banco e recursos de memória. Esta solução pode ser facilmente implementando com a interface Comparable.

Pool de Conexões

Use indiscutivelmente a abordagem de pool como paradigma de acesso ao banco de dados.

Problema: Eu realmente não sei o motivo, mas já peguei alguns aplicativos web por aí que abrem e fecham objetos de conexão com o banco de dados a cada requisição. Ou seja, a cada pedido enviado ao container java, no mínimo 2 chamadas remotas são efetuadas, uma para autenticar o usuário/senha e outra para efetuar a comando SQL desejado. Esta opção é uma das piores gafes que um desenvolvedor web pode fazer para deixar o sistema com a pior performance possível, sem falar que o sistema pode “baleiar” o banco quando o número de acesso simultâneos exceder a capacidade de resposta do determinado banco de dados.

Solução: Na web existe uma única solução comprovada que é o uso efetivo da abordagem de Pool de conexões. No momento da disponibilização – deploy da aplicação, o sistema deve abrir um numero X de conexões com o banco de dados que sera posteriormente usado em toda a aplicação. Esta abordagem mistura o conceito de compartilhamento e concorrência, sendo que pedidos em tempos diferentes reutilização a mesma conexão e pedidos simultâneos usarão diferentes conexões. Este numero X deve ser levantando e configurada de forma parametrizada de acordo com o perfil da aplicação e do modo/quantidades que os usuários estarão gastando conexões durante utilização do sistema.

Cache

Cachear informações que sofrem alto índice de acesso e baixa ocorrência de alteração.

Problema: Sistemas em geral implementam administração de informações na qual poderíamos classificar em 2 tipos: dados de manutenção/parâmetros e de processos:

  • Manutenção/Parâmetros - informações que os sistemas têm que guardar, usadas como parte do processo, que não possuem um fim nelas mesmas. Estes tipo de formação frequentemente sofre um baixo índice de manutenção e um alto número de acesso. Ou seja, no escopo da aplicação, estes dados raramente são alterados e muitos usados.
  • Processos - informações resultantes de processos com regras de negócio do escopo da aplicação. Estes podem ou não sofrer alterações e podem ou não ser altamente acessados. Tudo depende da natureza do negócio da aplicação.

A situação complicada seria o sistema fazer um acesso ao banco de dados a cada momento que diferentes usuários (concorrentes ou não) necessitam usar informações de manutenção, que na grande maioria dos casos são iguais. Ou seja, teríamos vários usuários acessando o banco de dados repetidas vezes para pegar as mesmas informações, gastando assim tempo e memória de forma desnecessária.

Solução: Analisar cuidadosamente e cachear as determinadas informações que se encaixam de alguma maneria no perfil de dados de “Manutenção/Parâmetros”. Conceitualmente, é fácil visualizar o mecanismos de cache: o primeiro usuário que necessitar da determinada informação efetuará um acesso ao banco e cacheará os dados em algum lugar na memória, fazendo com que os próximos usuários não precisem gastar tempo e memória repetindo o ciclo. O cache é atualizado quando estes dados forem atualizados de alguma maneira no sistema, bem como o perfil deles já mostrou que seria um caso difícil de acontecer.

Segue um resumo das estratégias gerais:

  • Use cache de global para evitar consultas repetitivas dentro da solução global para dados que não sofram alteração de nível global.
  • Use cache de sessão para evitar consultas repetitivas dentro da mesma sessão para casos no qual dados estes dados não sofram alteração durante a o tempo de duração da sessão.
  • Use cache de thread local para evitar consultas repetitivas dentro da mesma requisição para casos no qual estes dados os sofram alteração durantes as requisições.

A prática do cache, entretanto, não é algo simples ou trivial, demandando tempo e esforço para ser implementado. Eu poderia sugerir implementações prontas como o EhCache, ou serviços de cache disponibilizados pelos frameworks ORM. Veja que a utilização só vale a pena se os dados realmente possuírem um alto índice de acesso. Com esta abordagem, a aplicação consegue reduzir em média até 60% o acesso ao banco de dados.

Ciclo de Vida de Objetos

Controle efetivo da criação de objetos durante a execução do programa.

Problema: Algo que precisamos sempre lembrar durante a programação é que o operador new aloca fisicamente o objeto da memória, gastando espaço no HEAP da aplicação. A primeira questão problemática é que percebo que os programadores usam o new de forma displicente, sem nem ao menos para para pensar em que contexto da aplicação está usando. Tudo é motivo para fazer um new, eu já vi casos em que para reiniciar o estado do objeto, o programador dava um new na referência.

Solução: O programador tem que sair desse comodismo e começar a analisar todos os seus new! Duas perguntas resolvem o problema:

  1. Por que estou alocando esse objeto?
  2. Quantas vezes esse código vai ser executado?

Estas duas perguntas vão consciencializar o programador daquela situação, levando-o no mínimo a tomar uma das duas decisões abaixo:

  • Reutilizar Objetos: ao invés de ficar sempre fazendo new, ele pode aumentar a visibilidade do escopo daquele objeto e assim reusá-lo, restartando seu estado.
  • Reduzir o Escopo: reduzir o escopo dos objetos vai deixá-los disponíveis mais rápido para o coletor de lixo. Se for preciso, use escopos lógicos menores com { }.

Esta solução é uma das mais difíceis a serem implementadas, porque invadem a “cultura” do programador de se autocriticar. Mas quero animá-los dizendo que a batalha da economia de memória e performance se ganha na somatória de detalhes.

Objetos String

Controlar o gasto com os objetos Strings.

Problema: Outra coisa que sempre precisamos lembrar é que os objetos String chamados de “wrappers” são imutáveis, ou seja, uma vez instanciados eles nunca mudam. Qualquer operação com a String gerará uma terceira String. O problema aqui é o uso abusivo, desnecessário e inconsciente das String durante a execução do programa.

Solução: Segue a mesma idéia do tópico 5. O programador deve analisar a questão da necessidade e entender o porquê daquele uso. Duas opções surgem para contornar a situação:

  1. usar final static para as Strings que se encaixam no contexto de estáticas (SQLs, mensagens). Ou seja, somente será gasto um objeto para todas as execuções do programa.
  2. usar StringBuffer/StringBuilder para as String que sofrem alterações constantes ou para situações de manipulação de arquivos.

Configurações de memória da JVM

Problema: Mesmo depois de todas as precauções tomadas, a aplicação ainda pode gastar mais memória do valor default previamente configurado na JVM.

Solução: Neste casos, é preciso fazer um estudo, apurando a média de memória gasta pela aplicação, usando alguma ferramenta de profile e, assim, configurar um adequado número razoável de memoria.

É isso, pessoal. O artigo fica aberto para sugestões e novas idéias.

“De fato, sem fé é impossível agradar a Deus, porquanto é necessário que aquele que se aproxima de Deus creia que ele existe e que se torna galardoador dos que o buscam.” Hebreus 11:6

Maus Cheiros #6

631644-A-fralda-descartável-deve-ser-confortável-para-o-bebê.-Foto-divulgaçãoCadeias de Mensagens

Ocorre quando você encontra longas cadeias de mensagem quando um objeto pede para outro objeto, ao qual pede então outro objeto e assim por diante, gerando um acoplamento na cadeia de navegação. Diante disso, sempre tente ocultar ao máximo a delegação.

Intermediário

Ocorre quando você encontra um objeto que extrapola no uso da delegação para outros objetos, sem códigos adicionais, transformando o objeto em apenas um intermediário vazio e sem sentido. Nesse contexto, remova o intermediário fazendo com que o objeto cliente fale diretamente com o objeto que realmente sabe o que esta acontecendo.

Intimidade Inadequada

Ocorre quando você encontra classes manipulando partes privadas de outras, principalmente no uso de herança que permite que subclasses acessem partes protegidas. Seja radical com premissa básica do encapsulamento: sempre oculte o máximo que puder, evitando expor detalhes internos de um objeto.

Para todas as informações, veja o post inicial.

“Mas, seguindo a verdade em amor, cresçamos em tudo naquele que é a cabeça, Cristo,” Efésios 4:15

Curso de Java Básico Inicial – JSE M1

grande

“Por isso, também (Jesus) pode salvar totalmente os que por ele se chegam a Deus, vivendo sempre para interceder por eles.” Hebreus 7:25

Pattern Store Procedure Facade

imagesFerrContinuando o assunto das regras de negócio dentro do banco de dados, hoje eu gostaria de apresentar uma estrutura arquitetural que se tornou um design pattern muito utilizado dentro das equipes que eu trabalho, no qual eu nomeei de “Store Procedure Facade”. Segue a explicação do que é contexto e um exemplo prático:

Contexto

Dado uma solução, que em uma única requisição HTTP necessita acessar o banco de dados remoto N vezes para pegar informações de tabelas diferentes não relacionadas.

Problema

Dada uma situação de três acessos na requisição, 500 usuários simultâneos fazendo essa operação resultara em 1.500 round trips ao banco de dados, necessitando assim de aumento expressivo da configuração do DataSource, aumento no gasto de memória no parse do framework ORM e a redução exponencial do tempo de resposta da requisição.

Solução

Criar uma store procedure utilizada como se fosse uma Facade para encapsular, agrupar e retornar os N acessos de forma com que com um único round trip ao banco seja possível retornar todas as informações necessárias.

Exemplo

Segue um exemplo em SQL Server 2008:

CREATE PROCEDURE procecure_facade_x

AS

SELECT * INTO #parametro1 FROM tabelaX1

SELECT * INTO #parametro2 FROM tabelaX2

SELECT * INTO #parametro3 FROM tabelaX3

SELECT * FROM #parametro1, #parametro2, #parametro3

GO

Veja que é criada uma Store Procedure Facade que faz acesso em três tabelas diferentes, armazenando cada resultado em tabelas temporárias e depois agrupando e retornando como se fosse a uma mesma consulta.

Resultado

500 usuários simultâneos fazendo essa operação resultara em apenas 500 round trips no banco de dados, um por cada requisição, reduzindo expressivamente a configuração do DataSource, reduzindo os gasto de memória com parse do framework ORM e a aumento expressivo do tempo de resposta da requisição.

“Porque o Filho do Homem veio buscar e salvar o perdido.” Lucas 19:10

Não Faça Regras de Negócio Dentro do SGDB

240px-Stop_hand_nuvola.svgJá se passaram muitos anos desde a década de 90 e ainda assim eu continuo ouvindo e vendo muitas corporações colocando regra de negócio das soluções dentro de um banco de dados. Hoje o cenário de soluções corporativo é outro completamente mais complexo e a arquitetura em duas camadas não oferece características que sustente sua evolução. Resumidamente, soluções construídas em duas camadas não cumprem requisitos como escalabilidade, extensibilidade, manutenibilidade, segurança, perfomance e disponibilidade. Dessa forma, hoje usamos arquitetura n-camadas que é a única que, se bem projetada pode cumprir todos os requisitos não-funcionais característicos das soluções atuais. Segue um resumo básico dos velhos e já batido motivos documentados que justificam o não uso de regras dentro de um banco de dados:

1. Acoplamento

Regra dentro do banco de dados viola o princípio SOC que define que cada pedaço de um sistema precisa estar localizado em um lugar único e exclusivo promovendo isolamento, manutenção, reutilização e futura substituição, sem impacto nas outras partes.

2. Falta de Portabilidade

Regras escritas dentro do banco não oferecem portabilidade entre os diferentes produtos concorrentes da mesma filosofia de banco de dados adotado. Na década de 90 já era um transtorno e só tínhamos os famosos SGDB. Hoje então a coisa piorou com o surgimento com NoSQL e suas varias opções estruturais.

3. Péssima Manutenção e Código Inflexível

Regras no banco de dados são na maioria das vezes escritas usando store procedures  (filosofia procedural da década de 60) que são desprovidos de recursos e as diretrizes da OOP como encapsulamento, agregação, composição, associação, herança e polimorfismo e da mesma forma não conseguem usufruir de nenhum tipo de padrões (Patterns) arquiteturais, projetos e programação.

4. Know-How Especializado

Regras no banco de dados precisam ser manutenidas por profissionais que detenham conhecimento especializado para aquele particular produto e filosofia de banco de dados adotado, sendo difícil e ou caro de se encontrar mercado ou de se formar internamente.

5. Problemas de Performance

Soluções de grande porte, dotadas de um grande e crescente numero de acessos simultâneos e com a execução de regras pesadas vão degradando gradativamente a performance da solução, podendo até (que é o acontece na maioria dos casos) derrubar o serviço, uma vez que os banco de dados são desprovidos de devidos gerenciamentos de recursos encontrados normalmente em MIDDLEWARE relacionados com técnicas de otimizações, tunning, comunicação assíncronas, mensageira (MOM), escalabilidade vertical e horizontal apropriadas aplicadas especificamente na execução das regras de negócios.

6. Ausência de Recursos

Regras de negócio corporativas normalmente englobam o uso de recursos como logica binária, manipulação de arquivos PDF, DOC, XLS, XML, JSON.  Comunicação com sistemas externos como LDAP, SMTP, FTP, Mensageira, SOAP, REST etc. Os SGDB normalmente não possuem API’s disponíveis para estes fins e muitos outros recursos, salvo em casos raros que alguns provedores de SGDB fornece alguma coisa bem limitada e proprietária para tratar um ou outro. A coisa piora por que normalmente não existe abertura para se acrescentar uma API de terceiros para dentro do banco.

Conclusão

Martin Folwer no livro Patterns of Enterprise Application Architecture capitulo 8 escreveu:

“Por todas estas questões, muitas pessoas evitam implementar regras de negócio dentro de um banco de dados. Eu tento me alinhar com esta visão a menos que haja um grande ganho de desempenho a ser obtido, o que, para ser sincero frequentemente ocorre. Nesse caso, pego um método de negócio da camada de domínio e, alegremente  o transformo em um procedure dentro de um banco de dados. Faço isso apenas em áreas com claro problemas de desempenho, tratando-o como um abordagem de otimização e não como um principio arquitetural.”

Joshua Bloch no livro Java Effective capítulo escreveu no item 55 (que eu resumi):

“A historia das décadas passadas nos mostram que otimização prematura na verdade é mais propenso a causar danos do que benefícios. O caminho da otimização precoce pode leva-lo a uma solução que ainda não seja rápida, arquiteturalmente ruim e pior de tudo inflexível de difícil evolução. Portanto, não tente criar programas rápidos! Na verdade, foque em criar bons programas, usando todos os conceitos, princípios e abordagem necessários. Se um programa bem arquiteturado não for rápido suficiente, a boa arquitetura já estabelecida permitira que ele seja facilmente otimizado. Não pense em problemas de desempenho enquanto estiver projetando uma solução. Quando terminar a codificação, avalie seu desempenho. Se ele for suficientemente rápido, tudo estará resolvido. Caso contrário, localize a fonte de gargalo usando uma ferramenta de profile e trabalhe nas partes relevantes. Repita esse processo conforme necessário, avaliando o desempenho após cada alteração até apresentar um tempo satisfatório.”

Acredito que todos estes fatos já fornecem base suficiente para que você tenha condições de fazer a sua tomada de decisão :D . Até a próxima!

“Quão grande és tu, ó Soberano SENHOR! Não há ninguém como tu, nem há outro Deus além de ti, conforme tudo o que sabemos.” 2 Samuel 7:22

Aprenda o Escopo Thread-Local

Dentro da programação de uma solução usando a tecnologia Java, os desenvolvedores tem a responsabilidade diária de escolher corretamente em qual escopo de ciclo de vida seus objetos existiram. Em minhas consultorias em geral venho percebendo que a maioria dos profissionais Java desconhecem completamente um dos escopos mais interessantes e muito eficiente chamado de Thread-Local. A falta de uso desse escopo em uma solução pode gerar complicadores agravantes na arquitetura, levando a implementação na maioria das vezes para o caminho da complexidade e inflexibilidade. Diante esse cenário, hoje eu gostaria de apresentar o conceito e prática desse escopo, fazendo com que programadores e projetistas Java possam se equipar com mais esse poderoso recurso do JSE.

O que é Thread-Local?

Thread-local é considerado como mais um “escopo de acesso” como outros muitos existentes dentro do Java utilizado para definir o ciclo de vida dos objetos existentes durante a execução de um programa orientado a objetos. O grande diferencial desse escopo seja talvez pelo fato dele ser completamente baseado em conceitos de programação concorrente, fazendo com ele fique um pouco mais complicado de se entender e usar.

Como funciona?

O escopo Thread-Local possui dois tipos de comportamento distintos que lhe diferencia drasticamente dos outros. Poderíamos chamá-los de global e local.

Global – todos os objetos armazenados em uma Thread-Local são globais para a thread corrente de execução. Isso que dizer que os métodos de quaisquer objetos sendo executados dentro de uma mesma thread terão visibilidade para este escopo.

Local – todos os objetos armazenados em uma Thread-Local estarão restritos somente para objetos sendo executado dentro da determinada thread. Isso que dizer que os métodos de quaisquer objetos sendo executados em outras threads não terão visibilidade para este escopo.

O ponto de referencia para se assimilar a brincadeira é se conscientizar que quem executa um método de um objeto dentro de um programa Java é justamente algum objeto Thread, sendo ele criando manualmente ou não. Dentro de cada thread existe então um lugar especial que pode ser usado para armazenar objetos pertencentes especificamente a thread. Todos as chamadas de objetos que forem empilhados naquela thread terão visibilidade transparente para esse escopo, podendo então usa-lo para os mais diferentes propósitos.

Para tentar clarear mais as idéias, eu poderia dizer que o escopo Thread-Local funciona basicamente da mesma forma que o famoso e velho de guerra escopo estático. Todos os objetos colocados como estáticos no Java são criados apenas uma vez e ficam na memória até o termino da JVM. A única diferença é que os objetos colocados na Thread-Local ficam na memória até o termino da execução da determinada thread corrente. O escopo acaba, quando a Thread terminar de ser executada.

A figura abaixo apresenta um gráfico que ilustra a escopo estático:

Temos na figura acima dois objetos do tipo thread invocando concorrentemente métodos de alguns objetos compartilhados e outros objetos não compartilhados. Tudo que é colocado como estático é globalmente compartilhado para todas as threads e todos os objetos sendo executados dentro de cada thread.

Já na próxima figura temos a representação que ilustra o escopo Thread-Local:

Temos na ultima figura dois objetos do tipo thread invocando concorrentemente os mesmo objetos da primeira figura. Nesse caso em especifico, o escopo Thread-Local age como um compartimento opcional colocado dentro da cada thread, no qual todos os métodos invocados dentro daquela determinada thread tem acesso transparente, podendo usá-lo para qualquer fim necessário.

Quando usar Thread-Local

Use em qualquer lugar que necessite compartilhar objetos em nível de execução de Thread. Alguns possíveis exemplos seriam – parâmetros de sistema, parâmetros de usuários, parâmetros de autenticação e autorização e até parâmetros de processos. Um caso clássico de Thread-Local é a propagação de objetos de transação JDBC com chamadas recursivas. Objetos do padrão DAO poderiam usar este escopo para compartilhar a transação corrente, adicionando varias instruções SQL de diferentes objetos DAO. Frameworks que oferecem serviços transacionais para camadas de persistência como Spring utilizam-se dessa abordagem, podendo também ser facilmente implementada sem nenhum framework de terceiro.

A escopo Thread-Local é disponibilizado no Java usando a classe java.lang.ThreadLocal. Para todas as informações dessa classe, veja o JavaDoc.

Prática

Segue abaixo a implementação de um exemplo bem simples de Thread-Local.

A primeira classe chamada de ClasseThreadLocal foi utilizada para implementar a escopo Thread-Local. A segunda classe chamada de Regra foi utilizado para implementar o objeto que será executando concorrentemente dentro de varias threads. Veja que o método gravar() apenas acessa o valor dentro do escopo Thread-Local. Já a classe ProcessoThread  foi utilizado para implementar as threads que serão executadas em paralelo. Veja que o método run() armazena um objeto do tipo String dentro do escopo Thread-Local que será posteriormente acessível dentro do objeto implementado pela classe Regra. Por fim vemos a classe Exemplo implementando a método main() que faz toda a brincadeira acontecer. Veja que ele cria apenas um objeto Regra e quatro objeto ProcessoThread. Resumidamente, cada objeto thread executa concorrentemente o mesmo objeto Regra que transparentemente e separadamente acessa cada um valor diferente pertencente a sua própria thread de execução.

Observação

Existe uma certa situação em usar Thread-Local dentro de containers JEE, uma vez que a maioria deles otimizam gatos com a criação de objetos threads usando abordagem de pool. Por isso, os objetos colocados dentro do escopo Thread-Local em execuções dentro de containers JEE podem ficar não elegíveis para o GC após a execução, uma vez a thread volta para o pool e fica disponível para a próxima invocação. A forma correta de tratar isso é limpar o escopo Thread-Local no final de cada execução, chamando o método ThreadLocal.remove(). Eu fico por aqui e espero que o artigo te ajude a usar esse incrível escopo. Aquele abraço ;) .

“Porque, se perdoardes aos homens as suas ofensas, também vosso Pai celestial vos perdoará a vós”. Mateus 6:14

Seguir

Obtenha todo post novo entregue na sua caixa de entrada.

Junte-se a 670 outros seguidores