ThreadLocal is a commonly used utility class in the JDK's java.lang package that provides thread context functionality. Its most common use is probably storing key information such as the user context after login.
Thread context can also elegantly solve many seemingly complex problems, such as integrating the spring-mybatis framework:
When developing within the Spring ecosystem, we generally delegate transaction management to Spring, using the transactional annotation to enable transactions and configure details such as transaction isolation levels. We also access the database through the MyBatis framework rather than directly through JDBC. Spring and MyBatis are completely independent frameworks, yet when we use them, MyBatis seems able to obtain and use the database connection opened by Spring directly. We barely have to do anything beyond adding the mybatis-spring-boot-stater JAR.
The key to implementing this elegant design is ThreadLocal. The basic principle is that the stater JAR package will ultimately bring in a MybatisAutoConfiguration and inject it into our service’s bean container through SPI, in MybatisAutoConfiguration configured in mybatis required SqlSessionFactory, and injected Spring's own SpringManagedTransactionFactory (implementing TransactionFactory, the transaction factory is an extension mechanism provided by mybatis that allows transaction management to be delegated).
The overall execution flow is that a method annotated with transactional is ultimately proxied by AOP. In the before advice, the proxy class obtains a connection from the dataSource and eventually places it in a ThreadLocal (the ThreadLocal actually holds a map whose key is the dataSource and whose value is a connectionHolder, equivalent to the connection). Later, when mybatis executes SQL, it first goes through TransactionFactory Obtain the connection, because what is injected at this point TransactionFactory is SpringManagedTransactionFactory,so the connection corresponding to the dataSource is retrieved from ThreadLocal.
A brief introduction to how ThreadLocal works:
ThreadLocalMap It is a hash table that uses linear probing to resolve collisions. Each thread generates its own copy, and each ThreadLocal object contains one.threadLocalHashCode Used to calculate the hash key.
Comparing this with HashMap, ThreadLocalMap The choice of linear probing over separate chaining for collision resolution may be based on the following considerations:
1、Memory considerations: because their use cases differ, ThreadLocalMap generally contains far less data than HashMap. With small datasets, separate chaining incurs considerable additional space overhead for pointers
2, Efficiency: Building on 1, when the number of items is small, the space saved can be used to create a hash table with a lower load factor, reducing collisions and improving average lookup speed

Problem
A In tasks that use thread pools, context propagation is an issue, especially in the Tomcat-based web programming model. Because Tomcat itself uses a thread pool to handle request-response interactions between the server and clients, every web application has to handle correct thread context propagation manually.
B Efficiency issues: if an application uses too many ThreadLocal instances, lookup speed will decrease as the hash table grows
For problem A, the common solutions are fairly consistent. Here is the code from our system
This approach has two problems:
1、If additional ThreadLocal variables later need to be propagated correctly, the code must be modified to include them in the wrap logic; any omission will cause a bug
2, In some scenarios, a task may be executed by the submitting thread itself, for example:
When the thread pool is full and the thread pool's
RejectedExecutionHandlerusesCallerRunsPolicy, tasks submitted to the thread pool execute directly in the submitting thread, and the final cleanup logic then causes that thread’s context to be lostSimilarly, using
ForkJoinPool(including parallel executionStreamandCompletableFuture, usingForkJoinPool) scenarios, the expandedForkJoinTaskwill execute directly in the thread submitting the task. The final cleanup will likewise cause the submitting thread's context to be lost
Solution:
Question 1: If the ThreadLocal class supported an API to retrieve the total number of ThreadLocal objects currently in the application, the problem would be easy to solve. Unfortunately, it does not. If we insist on doing it, we can use reflection to access the thread object's inheritableThreadLocals (which is simply a ThreadLocalMap) property, then iterate over the hash table and retrieve all entries to obtain all ThreadLocal key-value pairs. Another approach is to add a wrapper around the JDK's ThreadLocal to supply the functionality described above.
Issue 2: This can be resolved by improving the wrap logic: when the thread first starts running, retrieve and temporarily save its current context, then replace it with the submitting thread’s context. Before execution ends, restore the thread’s context using the saved data instead of clearing it.
transmittable-thread-local
A ready-made solution already exists. Its implementation works along the two lines described above, and this process has been given a name CRR(capture/replay/restore) mode。
There are three ways to use it:
1 ### Modification Runnable and 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 Decorate the Thread Pool
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 ### Usage Java Agent to annotate JDK Thread pool implementation class
// ## 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();The advantage of using a Java agent is that it requires no intrusive changes to application code. As for the disadvantages, I think the biggest issue is that forgetting to add the parameters when starting the JVM could cause an incident.
Let's take a quick look at the source code logic
As for issue B, it is generally not considered in application development because sensitivity to time efficiency is not that high. However, performance optimization may receive more attention in the development of some underlying middleware. For example, netty chose to implement its own FastThreadLocal replaces the JDK’s ThreadLocal and also provides a companion FastThreadLocalThread,in FastThreadLocalThread and FastThreadLocal when used together, can always guarantee O(1) lookup time complexity. The solution trades space for time: to prevent hash collisions in the hash table from reducing lookup efficiency, it creates an array long enough that each FastThreadLocal A unique array index is allocated during construction, like giving each person their own slot. When retrieving a value, you can simply use your own index to fetch it directly from the array.
Here is a quick look at the source code