Students who develop in Java ThreadLocal
should not be unfamiliar with this class. There are many usage scenarios for this class, especially in some frameworks, such as database transaction operations, and data cross-layer transfer in the MVC framework. Here we briefly discuss ThreadLocal
the internal implementation and possible problems.
First ask yourself a question, how do you do it if you let yourself implement a functional class like this? The first reaction is to simply construct a Map<Thread, T>
data structure, the key is Thread
, and the value is the thread variable we want to save T
. Let's take a look at the problems with this design:
- As the running time is longer, there are more Threads in the Map. When the Thread exits, the resources are not released, and there is a memory leak problem.
- Because the Map data will be accessed by multiple threads and there is resource competition, it is necessary to perform synchronous and safe operations on the Map, which is inefficient.
The clever design in the JDK ThreadLocal
solves the above two problems. First of all, each Thread (thread) has a Map structure data inside ThreadLocalMap<ThreadLocal, T>
. When we assign a value to a thread ThreadLocal.set(T value)
variable , we actually first obtain Thread.currentThread())
the internal attribute field of the current thread, and then set the thread variable value T ThreadLocalMap
with the current key. ThreadLocal
The essence of this design is that each Thread
thread maintains a copy of its own ThreadLocalMap
data structure, which solves the second of the problems described above, with no race conditions.
Since each Thread
internally maintains a ThreadLocalMap
dictionary data structure, the key value of the dictionary is ThreadLocal
, when an ThreadLocal
object is no longer used (no other places are referenced), how does each thread that has been associated with it clear ThreadLocal
it in its internal ThreadLocalMap
What about this resource? The JDK ThreadLocalMap
has done another wonderful performance. It does not inherit java.util.Map
classes, but implements a dictionary structure specially used to regularly clean up invalid resources. Its internal storage entity structure is Entry<ThreadLocal, T>
inherited from java.lan.ref.WeakReference
, so that when it ThreadLocal
is no longer referenced, because of the weak reference mechanism, when the jvm finds that the memory is insufficient, it will automatically reclaim the instance memory pointed to by the weak reference, that is, the internal thread ThreadLocalMap
will release its ThreadLocal
reference to it. So that the jvm recycles the ThreadLocal
object. It is emphasized here that the object is recycled ThreadLocal
, not the whole Entry
, so the value object in the thread variable T
still exists in the memory, so the problem of memory leak has not been completely solved. Then analyze the implementation of the JDK, you will find that the invalid operation is periodically executed when calling ThreadLocal.get()
or . So this solves problem 1 of the above problems.ThreadLocal.set(T)
Entry
Is the problem really solved? It seems to be solved. Because there are no competing resource operations, there will be no memory leaks. But after thinking about it, I always feel that something is wrong. Is there really no memory overflow (OOM) problem? In the analysis of the above paragraph, it is emphasized that the internal invalid objects ThreadLocalMap
will be cleaned up regularly . The triggering condition is that it will be triggered when operations such as set, get, remove() are executed, but if there is such a scenario, when we execute it in a thread context A large internal data structure is set up, and then it should be cleared and reclaimed by reference, and the previous thread has been alive, then this large memory data object has not been reclaimed, and it is easy to write a code here to test the OOM. How to solve this problem?Entry
TrheadLocal
ThreadLocal.set(T)
ThreadLocal
T
The classes in Lucene org.apache.lucene.util.CloseableThreadLocal
solve the problem caused by the above special scenario: that is, to solve the problem of invalid object recycling in the JDK because it is performed periodically. CloseableThreadLocal
One is maintained internally ThreadLocal
. When executed CloseableThreadLocal.set(T)
, the internal is actually just a proxy to assign a value to the internal ThreadLocal
object, that is, to execute ThreadLocal.set(new WeakReference(T))
. When you see this, you should understand that it is not directly stored here T
, but it is packaged into a weak reference object. The purpose is that when the memory is insufficient, the jvm can recycle this object. But if you are careful, you will find that a new problem will be introduced, that is, when the current thread is still alive, the weak reference object is reclaimed due to insufficient memory, so that the get()
value will not be returned to null when the next call is made, which is unacceptable. . Therefore CloseableThreadLocal
, a data is also created internally WeakHashMap<Thread, T>
. When the thread is alive, at least one reference of T exists, so it will not be recycled in advance. However, the second problem introduced is to limit WeakHashMap
the synchronization of the operation synchronized
. You see, not everything is perfect, we just need to master that balance.