Gestão de Contexto com ThreadLocal: Implementando um Componente Reutilizável em Java

Em aplicações corporativas, frequentemente surge a necessidade de compartilhar informações contextuais entre diferentes camadas e métodos durante o processamento de uma requisição. Um exemplo clássico é a identificação do usuário autenticado, que precisa estar disponível em diversos pontos do fluxo de execução. Quando poucos dados precisam ser compartilhados, a passagem direta via parâmetros é viável. Contudo, à medida que o número de informações contextuais cresce, essa abordagem torna-se impraticável e prejudica a legibilidade do código.

O uso de ThreadLocal oferece uma alternativa elegante, permitindo que cada thread mantenha seu próprio estado isolado. Assim, qualquer método executado dentro da mesma thread pode acessar o contexto sem necessidade de propagação explícita de parâmetros. Apesar de funcional, o uso direto e disperso de múltiplas instâncias de ThreadLocal dificulta a manutenção e introduz riscos em ambientes com pools de threads, onde a reutilização de trabalhadores pode causar vazamento de dados entre requisições.

Para mitigar eses problemas, apresentamos a seguir uma implementação centralizada de gerenciador de contexto baseado em ThreadLocal.

Implementação do Gerenciador de Contexto

A classe abaixo encapsula toda a lógica de criação, acesso e destruição do contexto associado à thread corrente. Internamente, um ConcurrentHashMap armazena os pares chave-valor de cada contexto ativo.

package com.exemplo.concorrencia.contexto;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class GerenciadorContexto {

    private static final ThreadLocal<GerenciadorContexto> CONTEXTO_LOCAL = new ThreadLocal<>();

    private final ConcurrentMap<String, Object> repositorio = new ConcurrentHashMap<>();

    public static GerenciadorContexto obterInstancia() {
        return CONTEXTO_LOCAL.get();
    }

    public void armazenar(String chave, Object valor) {
        if (valor == null) {
            repositorio.remove(chave);
        } else {
            repositorio.put(chave, valor);
        }
    }

    public Object recuperar(String chave) {
        return repositorio.get(chave);
    }

    public static GerenciadorContexto iniciar() {
        if (CONTEXTO_LOCAL.get() != null) {
            throw new IllegalStateException("Já existe um contexto ativo para esta thread.");
        }
        GerenciadorContexto ctx = new GerenciadorContexto();
        CONTEXTO_LOCAL.set(ctx);
        return ctx;
    }

    public static void finalizar() {
        CONTEXTO_LOCAL.remove();
    }
}

Exemplo Prático: Serviço de Pedidos

Considere um sistema de e-commerce onde o serviço de pedidos precisa acessar dados do cliente, do produto e da quantidade durante o processo de finalização. O gerenciador de contexto elimina a neceessidade de repassar esses dados manualmente entre métodos.

package com.exemplo.concorrencia.contexto;

import com.exemplo.modelo.Produto;

public class ServicoPedidos {

    public void adicionarItem(Produto produto, int quantidade) {
        GerenciadorContexto.iniciar();
        try {
            GerenciadorContexto ctx = GerenciadorContexto.obterInstancia();
            ctx.armazenar("clienteId", obterClienteAtual());
            ctx.armazenar("produto", produto);
            ctx.armazenar("qtd", quantidade);

            System.out.println("Item adicionado ao carrinho...");

            processarCheckout();

        } finally {
            GerenciadorContexto.finalizar();
        }
    }

    private void processarCheckout() {
        GerenciadorContexto ctx = GerenciadorContexto.obterInstancia();
        String clienteId = (String) ctx.recuperar("clienteId");
        Produto produto = (Produto) ctx.recuperar("produto");
        int quantidade = (int) ctx.recuperar("qtd");

        System.out.println("Processando checkout...");
        System.out.println("Cliente: " + clienteId);
        System.out.println("Produto: " + produto.getNome());
        System.out.println("Quantidade: " + quantidade);
    }

    private String obterClienteAtual() {
        return "cli_789";
    }

    public static void main(String[] args) {
        ServicoPedidos servico = new ServicoPedidos();
        Produto p = new Produto();
        p.setNome("Notebook");
        p.setId(200);

        servico.adicionarItem(p, 2);
    }
}

Facilitadores com Expressões Lambda

Para reduzir a verbosidade do bloco try/finally repetido em cada ponto de uso, podem ser adicionados métodos estáticos que aceitam tarefas funcionais (Runnable e Supplier), garantindo sempre a limpeza do contexto ao final.

package com.exemplo.concorrencia.contexto;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;

public class GerenciadorContexto {

    private static final ThreadLocal<GerenciadorContexto> CONTEXTO_LOCAL = new ThreadLocal<>();
    private final ConcurrentMap<String, Object> repositorio = new ConcurrentHashMap<>();

    // ...métodos anteriores omitidos...

    public static <E extends Throwable> void executar(Runnable tarefa) throws E {
        iniciar();
        try {
            tarefa.run();
        } finally {
            finalizar();
        }
    }

    public static <T, E extends Throwable> T computar(Supplier<T> fornecedor) throws E {
        iniciar();
        try {
            return fornecedor.get();
        } finally {
            finalizar();
        }
    }
}

Propagação de Contexto em Pools de Threads

Em cenários assíncronos, onde tarefas são submetidas a um pool de threads, o contexto vinculado à thread original não está disponível nos trabalhadores que executarão a tarefa. Para resolver isso, é necessário capturar o contexto antes de submeter a tarefa e restaurá-lo no início da execução do trabalhador, removendo-o ao final. Essa abordagem garante que o estado transitório seja propagado de forma segura, sem contaminar threads reutilizadas.

Tags: java ThreadLocal ConcurrentHashMap ThreadPool Context Management

Publicado em 7-7 16:05