This article is participating in the “Java Theme Month – Java Brush questions card”, activity link

The title

ThreadLocal source code analysis

knowledge

Basic introduction

  • ThreadLocal is the maintenance class for ThreadLocalMap, a local variable inside a Thread. When a thread holds multiple ThreadLocal operations, it looks in the ThreadLocalMap by key.

  • Each Thread maintains a ThreadLocal. ThreadLocalMap variables, the underlying storage structure for Entry [], ThreadLocal instance as ThreadLocalMap key, set/get value is the value of the Map, among them, A reference to key is a weak reference.

  • When performing a ThreadLocal. Set, is actually a ThreadLocal object and value through the key – in the form of value in the Thread of the ThreadLocal. ThreadLocalMap properties, completed the Thread isolated storage, to ensure the safety of the Thread, Therefore, each thread can change its own copy independently without affecting the corresponding copy of other threads.

Usage scenarios

  • ThreadLocal appears indirectly in frameworks you use, even if you don’t use it directly:

    1. Spring transaction management.
    2. Hibernate session management.
    3. Logback (and log4j) MDC functionality implementation, etc.
    4. For example, some of the paging functions used are also implemented using ThreadLocal.
    5. ThreadLocal is generally used for traceId in full link tracing or context transfer in process engine.
    6. The implementation of Spring MVC’s RequestContextHolder uses ThreadLocal;

General overview

ThreadLocal is commonly used for thread isolation. Here we’ll explain the implementation principles, design concepts, internal implementation details (maps, weak references), and memory leaks.

Role purpose

Provide a thread public variable to reduce the complexity of passing some public variables between multiple functions or components in the same thread, so that the thread’s local variables are isolated.

The principle of overview

Internal structure drawing

Reference logic diagram (dotted lines indicate weak references)

The principle of analysis

  • Within a single Thread can save multiple ThreadLocal object, the location of the store is located in the Thread of the ThreadLocal. ThreadLocalMap variables, in the Thread has the following variables:
/* ThreadLocal values pertaining to this thread. * This map is maintained * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
Copy the code

ThreadLocalMap is a static inner class maintained by ThreadLocal, as noted in the code note that this variable is maintained by ThreadLocal.


When we use the get() and set() methods of ThreadLocalMap, we actually call the corresponding get() and set() methods of ThreadLocalMap.

  • This variable in Thread is usually initialized when ThreadLocal’s get() and set() methods are first called.
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if(map ! =null)
        map.set(this, value);
    else
        createMap(t, value);
}
Copy the code
  • In the set method above, we get the current thread object first, and then use getMap to get threadLocals from the current thread:
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
Copy the code
  • If the corresponding attribute in Thread is null, create a ThreadLocalMap and assign it to Thread:
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Copy the code
  • If it already exists, set the value using the set method of ThreadLocalMap. Here we can see that the set key is this, which is the current ThreadLocal object, and the value is the value we want to store.

The corresponding get method source is as follows:

public T get(a) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if(map ! =null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if(e ! =null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            returnresult; }}return setInitialValue();
}
Copy the code

As you can see, also from the current thread, get the threadLocals property of the current thread and return the stored value from it. During get, if the threadLocals property of the Thread is not initialized, the createMap method is indirectly called to initialize the Thread.

The data structure

  • ThreadLoalMap is a static internal class in ThreadLocal that is similar to a HashMap data structure but does not implement a Map interface.

  • ThreadLocalMap initializes an Entry array of size 16. The Entry object is used to hold each key-value pair. We already know from the set method above that the key is always a ThreadLocal object.

Take a look at the relevant source code:

static class ThreadLocalMap {

    static class Entry extends WeakReference<ThreadLocal<? >>{
        /** The value associated with this ThreadLocal. */Object value; Entry(ThreadLocal<? > k, Object v) {super(k); value = v; }}private static final int INITIAL_CAPACITY = 16;
    // ...
}
Copy the code

The class diagram structure of ThreadLoalMap is as follows:

The important thing to note here is that the Entry object in the ThreadLocalMap class inherits from a WeakReference, which means it is a WeakReference.

Since ThreadLocalMaps are created lazily, at least one Entry object is created during construction. This can be seen in the constructor:

ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) { table =new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}
Copy the code

The above constructor creates an Entry array with a default length of 16 and uses the hashCode and length bits to determine the index value I. As mentioned above, each Thread has a variable of type ThreadLocalMap.

So far, combined with Thread, we can see the entire data model as follows:

Hash conflict and resolution

  • Note that in the constructor the Entry location in the table is obtained using the Hashcode algorithm. Each ThreadLocal object has a hash value of threadLocalHashCode, and each time a ThreadLocal object is initialized, the hash value increases by a fixed size 0x61C88647.

  • When an Entry object is stored into an Entry value in a ThreadLocalMap, it is positioned to position I in the table based on the hash value of the ThreadLocal object.

There are three cases:

  • If the current location is empty, the Entry is directly stored in the corresponding location.

  • If position I already has a value and the key of the Entry object happens to be the key to be set, then reset the value in the Entry.

  • If the Entry object of position I is unrelated to the key to be set, look for an empty position;

Hash conflicts occur when calculating hash values. Common solutions include rehash, open address, public overflow area, and chained address.

  • The above process can be seen here is open address method, if the current position value, will continue to look for the next position, pay attention to the table [len – 1] is the next position table [0], like a circular array, so also called closed hashing.

  • If you can’t find the empty space all the time, an infinite loop will occur, and memory will overflow. Of course, there is a mechanism to expand capacity, won’t find empty position commonly.

Memory leaks

Improper use of ThreadLocal can result in memory leaks, which in turn can lead to memory overruns. Let’s take a look at the causes of memory leaks and related design ideas.

Memory reference link
  • Each Thread maintains a ThreadLocalMap, whose key is the ThreadLocal instance itself, and whose value is the Object that the business needs to store.

  • ThreadLocal itself does not store values; it simply acts as a key for the thread to retrieve values from ThreadLocalMap.

  • Take a close look at the ThreadLocalMap, which uses a weak reference to ThreadLocal as its Key, and the weakly referenced object is reclaimed during GC. So with ThreadLocal, the chain of references looks like this:

Cause analysis of leakage
  • Normally, threads are destroyed when they are finished executing, and the ThreadLocalMap instance pointed to by Thread.threadlocals becomes garbage and the Entity stored in it is reclaimed. There is no memory leak in this case.

  • The memory leak scenario usually exists in the case of thread pools. In this case, the Thread life cycle is long, and the threadLocals reference will always exist. When the ThreadLocal stored in it is reclaimed (the weak reference life cycle is short), the Entity will become an instance with a null key, but the value will not be reclaimed.

  • If the Entity is never get(), set(), or remove(), it is never reclaimed and a memory leak occurs.

Therefore, the remove() method is usually called after ThreadLocal is used to clean up memory.

Why weak references instead of strong references?

The apparent root cause of the memory leak is the use of weak references, but why did the JDK adopt a weak-reference implementation instead of a strong reference?

Let’s start with a comment on the ThreadLocalMap class:

To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.

WeakReference is used as a key in hash table entries to help deal with scenarios with large data and long life cycle.

This is somewhat different from what we might expect, but weak references are specifically used to solve memory storage problems.

Let’s assume that if the key uses a strong reference, then all other objects that hold ThreadLocal references are reclaimed, but ThreadLocalMap still holds a strong reference to ThreadLocal. This prevents the ThreadLocal from being reclaimed, resulting in an Entry memory leak.

  • Compare that with weak references. Objects that hold ThreadLocal references are reclaimed, and ThreadLocalMap holds weak references to ThreadLocal that are automatically reclaimed.

(Prevent users from getting data they shouldn’t get because it’s already been recycled!)

  • The corresponding value, however, needs to be cleared the next time the set/get/remove methods are called.

When ThreadLocalMap calls set, get, or remove, the key will be null and the corresponding value will be removed.

So, the root cause of a memory leak is whether or not a manual cleanup operation, not a weak reference.

Stretch out in all directions

Within a single Thread can save multiple ThreadLocal object, the location of the store is located in the Thread of the ThreadLocal. ThreadLocalMap variables, in the Thread has the following variables:

Why put the Map in each Thread

If the Map is maintained in ThreadLocal, ConcurrentHashMap is used to reduce concurrency contention, but in form there is still contention between threads. Maintaining the Map independently in each thread satisfies the design concept of thread isolation.

What’s the difference between a ThreadLocal. ThreadLocalMap and HashMap
Different methods are used to resolve hash conflicts
  • A HashMap is stored in an array plus a linked list. When a hash conflict occurs, the list is appended.

  • ThreadLocal. ThreadLocalMap USES is open addressing method, namely, looking for the next position is not stored data.

Extension: Resolving hash conflicts 1. Open addressing 2. Rehash 3. Chain address method 4. Public overflow area.

Different capacity expansion mechanisms

When the ThreadLocal. ThreadLocalMap data size is larger than 1/2, 2 times will increase.

Why weak references are used to store Entry keys

When there is no reference ThreadLocal, ThreadLocal ThreadLocalMap still exists in the Thread, and ThreadLocal corresponding Entry will never be used to, so have adopted a weak reference, when there is no reference ThreadLocal, The automatic key is collected by the GC.

Why is the Entry value store not using weak references

The object we store has no reference other than the Value of ThreadLocalMap. If the Value is a weak reference to the object, it will be reclaimed during GC and the object will not be accessible, which is obviously not desirable.

How to solve

  • If ThreadLocal is not in use, call the remove method to remove Entry from the Map.

  • The set method for Java8 ThreadLocalMap prevents memory leaks by calling the replaceStaleEntry method to reclaim the value of an Entry object with a null key (that is, a concrete instance) as well as the Entry object itself

  • The get method indirectly calls the expungeStaleEntry method to set an Entry with a null key and value to NULL so that the Entry can be reclaimed