ThreadLocal é uma classe utilitária bastante usada do pacote java.lang do JDK que implementa a funcionalidade de contexto de thread. Sua aplicação mais comum provavelmente é armazenar informações essenciais, como o contexto do usuário após o login.
O contexto da thread também pode resolver com elegância muitos problemas que parecem bastante complexos, por exemplo: a integração do framework spring-mybatis:
Ao desenvolver com o ecossistema spring, geralmente delegamos a gestão das transações ao spring, usando a anotação transactional para configurar a abertura das transações, o nível de isolamento e outras informações. Também não acessamos o banco de dados diretamente pelo jdbc, mas pelo framework mybatis. spring e mybatis são dois frameworks completamente independentes, mas, quando os usamos, parece que a conexão com o banco de dados aberta pelo spring pode ser obtida e utilizada diretamente pelo mybatis. Praticamente não precisamos fazer nada: basta incluir um pacote jar mybatis-spring-boot-stater.
O ponto central para implementar esse projeto elegante é ThreadLocal. Em linhas gerais, o princípio é que o pacote JAR stater acabará incluindo para nós um MybatisAutoConfiguration e injetado por meio de SPI no contêiner de beans do nosso serviço, em MybatisAutoConfiguration já configurado em mybatis necessário SqlSessionFactory,e injetou o próprio componente do Spring SpringManagedTransactionFactory(implementa TransactionFactory,a fábrica de transações é um mecanismo de extensão fornecido pelo mybatis, que permite delegar o gerenciamento de transações).
O fluxo geral de execução é o seguinte: o método com a anotação transactional acaba sendo intermediado por um proxy AOP. No advice anterior à execução, a classe proxy obtém uma connection do dataSource e a coloca em um ThreadLocal (na verdade, o ThreadLocal armazena um map cuja chave é o dataSource e cujo valor é o connectionHolder, equivalente à connection). Depois, quando o mybatis executa SQL, primeiro usa TransactionFactory Obter a connection, pois, nesse momento, o que foi injetado TransactionFactory é SpringManagedTransactionFactory,por isso, a connection correspondente ao dataSource será obtida do ThreadLocal.
Breve introdução ao funcionamento do ThreadLocal:
ThreadLocalMap é uma tabela hash que usa sondagem linear para resolver colisões. Cada thread gera sua própria cópia, e cada objeto ThreadLocal contém um threadLocalHashCode Usado para calcular a chave hash.
Comparando com o HashMap,ThreadLocalMap A escolha da sondagem linear como algoritmo de resolução de colisões, em vez do encadeamento, pode se basear nas seguintes considerações:
1、Considerações de memória: como os cenários de uso são diferentes, o volume de dados de ThreadLocalMap geralmente é muito menor que o de HashMap; com poucos dados, o encadeamento separado gera um consumo adicional considerável de espaço para ponteiros
2、Eficiência: com base em 1, quando a quantidade é pequena, o espaço economizado pode ser usado para gerar uma tabela hash com um fator de carga menor, reduzindo colisões e aumentando a velocidade média de busca

Problema
A Em cenários de tarefas baseadas em pools de threads, existe o problema de propagação de contexto, especialmente no modelo de programação web baseado em Tomcat. Como o próprio Tomcat usa um pool de threads para lidar com as interações de requisição e resposta entre servidor e cliente, cada aplicação web precisa tratar manualmente da propagação correta do contexto das threads.
B Problema de eficiência: se houver muitas instâncias de ThreadLocal na aplicação, a expansão da tabela hash reduzirá a velocidade das buscas
Para o problema A, as soluções comuns são bastante semelhantes; vou colar diretamente o código do nosso sistema
Essa solução apresenta dois problemas:
1、Se posteriormente for adicionado um novo ThreadLocal que precise ser transmitido corretamente, será necessário alterar o código para incluí-lo na lógica de wrap; qualquer omissão causará um bug
2、Em alguns cenários, a tarefa pode ser executada pela própria thread que a submeteu, por exemplo:
Quando o pool de threads estiver cheio e o seu
RejectedExecutionHandlerutilizaCallerRunsPolicyquando isso ocorre, a tarefa enviada ao pool de threads é executada diretamente na thread que a enviou, e a lógica final de limpeza provoca a perda do contexto dessa threadDa mesma forma, usando
ForkJoinPool(incluindo a execução paralelaStreameCompletableFuture, a implementação interna usaForkJoinPool)nesse cenário, o que foi expandidoForkJoinTaskserá executado diretamente na thread que submete a tarefa. A limpeza final também causará a perda do contexto dessa thread
Solução:
Problema 1:se a classe ThreadLocal oferecesse uma API que permitisse obter quantos objetos ThreadLocal existem atualmente na aplicação, o problema estaria resolvido; infelizmente, ela não oferece. Se for necessário forçar uma solução, é possível usar reflexão para acessar, no objeto thread,inheritableThreadLocals(é justamente um ThreadLocalMap)e, em seguida, percorrer a tabela hash para extrair todas as entradas e obter todos os pares de chave e valor de threadlocal. Outra abordagem é criar mais uma camada de encapsulamento para o ThreadLocal do jdk, acrescentando as funcionalidades mencionadas acima.
Problema 2: pode ser resolvido aprimorando a lógica de wrap: quando a thread começar a executar, primeiro obtenha e armazene temporariamente seu contexto atual; depois, substitua esse contexto pelo da thread que submeteu a tarefa. Antes de encerrar a execução, em vez de limpar o contexto, restaure o contexto original da thread usando os dados armazenados temporariamente.
transmittable-thread-local
Já existe uma solução pronta, cujo princípio de implementação parte das duas abordagens acima, e esse processo recebeu um nome CRR(capture/replay/restore) modo。
Há três formas de uso:
1 ### Modificação Runnable e Callable
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
// =====================================================
// 在父线程中设置
context.set("value-set-in-parent");
Runnable task = new RunnableTask();
// 额外的处理,生成修饰了的对象ttl
Runnable Runnable ttlRunnable = TtlRunnable.get(task);
executorService.submit(ttlRunnable);
// =====================================================
// Task中可以读取,值是"value-set-in-parent"
String value = context.get();2 Decorar o pool de threads
ExecutorService executorService = ...
// 额外的处理,生成修饰了的对象
executorService executorService = TtlExecutors.getTtlExecutorService(executorService);
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
// =====================================================
// 在父线程中设置
context.set("value-set-in-parent");
Runnable task = new RunnableTask();
Callable call = new CallableTask();
executorService.submit(task);
executorService.submit(call);
// =====================================================
// Task或是Call中可以读取,值是"value-set-in-parent"
String value = context.get();3 ### Uso Java Agent para modificar JDK classe de implementação do pool de threads
// ## 1. 框架上层逻辑,后续流程框架调用业务 ##
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
context.set("value-set-in-parent");
// ## 2. 应用逻辑,后续流程业务调用框架下层逻辑 ##
ExecutorService executorService = Executors.newFixedThreadPool(3);
Runnable task = new RunnableTask();
Callable call = new CallableTask();
executorService.submit(task);
executorService.submit(call);
// ## 3. 框架下层逻辑 ##
// Task或是Call中可以读取,值是"value-set-in-parent"
String value = context.get();A vantagem de usar a abordagem com Java Agent é que ela não exige alterações no código da aplicação, e, quanto à desvantagem, acho que o maior problema é que, se você esquecer de adicionar os parâmetros ao iniciar a JVM, isso pode causar um incidente.
Uma breve análise da lógica do código-fonte
Quanto ao problema B, geralmente ele não é levado em conta no desenvolvimento de aplicações, pois a sensibilidade à eficiência de tempo não é tão alta. No entanto, no desenvolvimento de alguns middlewares de baixo nível, a otimização de desempenho pode receber mais atenção. Por exemplo, o netty optou por implementar por conta própria FastThreadLocal Substitui o ThreadLocal do jdk e também fornece o respectivo FastThreadLocalThread,em FastThreadLocalThread e FastThreadLocal Quando usados em conjunto, é possível garantir sempre uma complexidade de tempo de busca O(1). O princípio dessa solução é trocar espaço por tempo: para evitar que colisões de hash na tabela hash reduzam a eficiência das consultas, é criado um array suficientemente longo, cada FastThreadLocal Durante a construção, é atribuído um índice exclusivo no array, como uma posição reservada para cada pessoa; na consulta, basta usar seu próprio index para obter o valor diretamente do array.
Vou apresentar brevemente o código-fonte