Aprendendo a usar o escopo Thread-Local Java

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