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.